postMessage可太有用了


前言: 本篇文章我将带大家一起来好好认识一下postMessage,包括它的兼容性,对应的API介绍,以及常见的几个使用场景,希望可以给有同样困惑的盆友们一点启发,给需要用这个技术的同僚们一些帮助.

postMessage的定义

postMessage是html5引入的API,postMessage()方法允许来自不同源的脚本采用异步方式进行有效的通信,可以实现跨文本文档,多窗口,跨域消息传递.多用于窗口间数据通信,这也使它成为跨域通信的一种有效的解决方案.

 postMessage的兼容性

下图是在caniuse上面搜到的postMessage兼容性截图,除IE浏览器的支持度比较低外,,其他浏览器的支持度良好.

 postMessage API介绍

发送数据: 

otherWindow.postMessage(message, targetOrigin, [transfer]);

otherWindow

窗口的一个引用,比如iframe的contentWindow属性,执行window.open返回的窗口对象,或者是命名过的或数值索引的window.frames.

message

要发送到其他窗口的数据,它将会被[!结构化克隆算法](https://developer.mozilla.org/en-US/docs/DOM/The_structured_clone_algorithm)序列化.这意味着你可以不受什么限制的将数据对象安全的传送给目标窗口而无需自己序列化.

targetOrigin

通过窗口的origin属性来指定哪些窗口能接收到消息事件,指定后只有对应origin下的窗口才可以接收到消息,设置为通配符"*"表示可以发送到任何窗口,但通常处于安全性考虑不建议这么做.如果想要发送到与当前窗口同源的窗口,可设置为"/"

transfer | 可选属性

是一串和message同时传递的**Transferable**对象,这些对象的所有权将被转移给消息的接收方,而发送一方将不再保有所有权.

接收数据: 监听message事件的发生

window.addEventListener("message", receiveMessage, false) ;

function receiveMessage(event) {

    var origin= event.origin;

    console.log(event);

}

event对象的打印结果截图如下:

这里重点介绍event对象的四个属性

data : 指的是从其他窗口发送过来的消息对象;

type: 指的是发送消息的类型;

source: 指的是发送消息的窗口对象;

origin:  指的是发送消息的窗口的源

postMessage的使用场景

场景一 跨域通信(包括GET请求和POST请求)

 我们都知道JSONP可以实现解决GET请求的跨域问题,但是不能解决POST请求的跨域问题.而postMessage都可以.这里只是列举一个示例,仅供参考,具体的代码如何编写要以具体的场景而定奥~

父窗体创建跨域iframe并发送信息

<!DOCTYPE html>

<html>

    <head>

        <meta charset="utf-8">

        <meta http-equiv="X-UA-Compatible" content="IE=edge">

        <title>跨域POST消息发送</title>

        <script type="text/JavaScript">   

            // sendPost 通过postMessage实现跨域通信将表单信息发送到 moweide.gitcafe.io上,

            // 并取得返回的数据   

            function sendPost() {       

                // 获取id为otherPage的iframe窗口对象       

                var iframeWin = document.getElementById("otherPage").contentWindow;       

                // 向该窗口发送消息       

                iframeWin.postMessage(document.getElementById("message").value, 'http://moweide.gitcafe.io');   

            }   

            // 监听跨域请求的返回   

            window.addEventListener("message", function(event) {       

                console.log(event, event.data);   

            }, false);

        </script>

    </head>

    <body>

        <textarea id="message"></textarea>

        <input type="button" value="发送" onclick="sendPost()">

        <iframe

            src="http://moweide.gitcafe.io/other-domain.html" id="otherPage" style="display:none"></iframe>

    </body>

</html>

子窗体接收信息并处理

<!DOCTYPE html>

<html>

    <head>

        <meta charset="utf-8">

        <meta http-equiv="X-UA-Compatible" content="IE=edge">

        <title>POST Handler</title>

        <script src="//code.jquery.com/jquery-1.11.0.min.js"></script>

        <script type="text/JavaScript">

            window.addEventListener("message", function( event ) {

                // 监听父窗口发送过来的数据向服务器发送post请求

                var data = event.data;

                $.ajax({

                    // 注意这里的url只是一个示例.实际练习的时候你需要自己想办法提供一个后台接口

                    type: 'POST',

                    url: 'http://moweide.gitcafe.io/getData',

                    data: "info=" + data,

                    dataType: "json"

                }).done(function(res){       

                    //将请求成功返回的数据通过postMessage发送给父窗口       

                    window.parent.postMessage(res, "*");   

                }).fail(function(res){       

                    //将请求失败返回的数据通过postMessage发送给父窗口       

                    window.parent.postMessage(res, "*");   

                });

            }, false);

        </script>

    </head>

    <body></body>

</html>

场景二  WebWorker

JavaScript语言采用的是单线程模型,通常来说,所有任务都在一个线程上完成,一次只能做一件事,后面的任务要等到前面的任务被执行完成后才可以开始执行,但是这种方法如果遇到复杂费时的计算,就会导致发生阻塞,严重阻碍应用程序的正常运行.Web Worker为web内容在后台线程中运行脚本提供了一种简单的方法,线程可以执行任务而不干扰用户界面.一旦创建,一个worker可以将消息发送到创建它的JavaScript代码,通过消息发布到改代码指定的事件处理程序.

一个woker是使用一个构造函数创建一个对象,运行一个命名的JavaScript文件-这个文件将包含在工作线程中运行的代码,woker运行在另一个全局上下文中,不同于当前的window,不能使用window来获取全局属性.

一些局限性

只能加载同源脚本文件,不能直接操作DOM节点

Worker 线程不能执行alert()方法和confirm()方法,但可以使用 XMLHttpRequest 对象发出 AJAX 请求

无法读取本地文件,只能加载网络文件

也不能使用window对象的默认方法和属性,然而你可以使用大量window对象之下的东西,包括webSocket,indexedDB以及FireFoxOS专用的D阿塔Store API等数据存储机制.查看Functions and classes available to workers获取详情。

workers和主线程间的数据传递通过这样的消息机制进行——双方都使用postMessage()方法发送各自的消息,使用onmessage事件处理函数来响应消息(消息被包含在Message事件的data属性中)。这个过程中数据并不是被共享而是被复制;woker分为专用worker和共享worker,一个专用worker紧急能被首次生成它的脚本使用,而共享woker可以同时被多个脚本使用.

专用woker使用示例:

// main.js

if(window.Worker) {

    var myWorker = new Worker('http://xxx.com/worker.js');

    // 发送消息

    first.onchange = function() {

        myWorker.postMessage([first.value, second.value]);

        console.log("Message posted to worker");

    }

    second.onchange = function() {

      myWorker.postMessage([first.value,second.value]);

      console.log('Message posted to worker');

    }

    // 主线程 监听onmessage以响应worker回传的消息

    myWorker.onmessage = function (e) {

      var textContent = e.data;

      console.log("message received from worker"); 

    }

}

// worker.js

// 内置selfduixiang,,代表子线程本身, worker内部要加载其他脚本,可通过importScripts()方法

onmessage = function(e) {

    console.log("message received from main script");

    var workerResult = "Result: " + (e.data[0] * e.data[1]);

    console.log("posting message\back to main script");

    postMessage(workerResult);

}

Web Worker的使用场景,用于收集埋点数据,可以用于大量复杂的数据计算,复杂的图像处理,大数据的处理.因为它不会阻碍主线程的正常执行和页面UI的渲染.

埋点数据采集下的使用: 可在main.js中收集数据,将收集到的信息通过postMessage的方式发送给worker.js,在woker.js中进行相关运算和整理并发送到服务器端;当然,不使用Web Woker,通过在单页面应用中的index.html中创建iframe也可以实现页面间切换,页面停留时长等数据的采集,具体的实现我就不细讲了,感兴趣的同学可在网上搜索解决方案,有什么疑问欢迎私信我~~~

场景三  Service Worker

可在浏览器控制台的application中里看到Service Worker的存在

Service Worker是web应用做离线存储的一个最佳的解决方案,Service Worker和Web Worker的相同点是在常规的js引擎线程以外开辟了新的js线程去处理一些不适合在主线程上处理的业务,不同点主要包括以下几点:

Web Worker式服务于特定页面的,而Service Worker在被注册安装之后能够在多个页面使用

Service Worker常驻在浏览器中,不会因为页面的关闭而被销毁.本质上,它是一个后台线程,只有你主动终结,或者浏览器回收,这个线程才会结束.

生命周期,可调用的API也不同

我们可以使用Service Worker来进行缓存,用js来拦截浏览器的http请求,并设置缓存的文件,从而创建离线web应用.关于Service Worker的概念的介绍就到这里~~,感兴趣的可以找相关文章学习,有疑问的欢迎私信与我探讨~,这里我们主要介绍的是使用postMessage方法进行Service Worker和页面之间的通讯.

从页面发送信息到Service Worker

需要注意一点,这个页面如果直接扔进浏览器里(使用的是file协议)打开是会报错的,要使用nginx做端口映射,或者用node搭建一个服务器(使用http协议)来访问该页面(目前我猜测的原因是浏览器对file协议打开的文件做了一些服务的限制,如果有大佬知道具体原因还望告知).下文也将附上我的nginx做端口映射的配置.

<!DOCTYPE html>

<html>

<head>

<meta charset="utf-8">

<meta http-equiv="X-UA-Compatible" content="IE=edge">

<title>Service Worker跨窗口通信</title>

</head>

<body>

    <textarea id="showArea"></textarea>

    <script src="sw1.js"></script>

    <script src="sw2.js"></script>

    <script type="text/JavaScript">

        if('serviceWorker' in window.navigator) {

            // 对于多个不同scope的多个Service Worker,我们也可以给指定的Service Worker发送消息

            navigator.serviceWorker.register('./sw1.js', { scope:'./sw1'})

                .then(function(reg) {

                    console.log('success', reg);

                    return new Promise((resolve, reject) => {

                        const interval = setInterval(function() {

                            if(reg.active) {

                                clearInterval(interval);

                                resolve(reg.active);

                            }   

                        }, 1000);

                    }).then(sw => {

                        sw.postMessage("this message is from page to sw1");

                    })


                })

            navigator.serviceWorker.register('./sw2.js', { scope:'./sw2'})

                .then(function(reg) {

                    console.log('success', reg);

                    return new Promise((resolve, reject) => {

                        const interval = setInterval(function() {

                            if(reg.active) {

                                clearInterval(interval);

                                resolve(reg.active);

                            }   

                        }, 1000);

                    }).then(sw => {

                        sw.postMessage("this message is from page to sw2");

                    })


                });

                navigator.serviceWorker.addEventListener('message', function (event) {

                    console.log(event.data);

                    // 接受数据,并填充在 DOM 中

                    document.getElementById('showArea').value = event.data ;

                });

        }


    </script>

</body>

</html>

// sw1.js

self.addEventListener("message", function(event) {

    console.log("sw1.js " + event.data);

    event.source.postMessage('this message is from sw1.js, to page');

});

// sw2.js

self.addEventListener("message", function(event) {    console.log("sw2.js " + event.data);    event.source.postMessage('this message is from sw2.js, to page');  // event.source是消息来源页面对象的引用});

nginx做端口映射的相关配置:

// nginx.conf

// 因为有多个项目会用到nginx服务做端口映射

// 所以我在nginx的目录下新建了conf.d的文件来存放每个项目的配置.

// 然后在主配置文件里通过include引入

http {   

    # 这里省略了一些你本机电脑上的nginx服务的配置   

    include conf.d/*.conf;

}

// testHtml.conf

server {   

    listen 9090;   

    server_name      localhost;

    location / {       

        root  C:/Users/hzljie/Desktop/test/testb; 

        # 这是我的测试页面的存放路径,读者用的时候记得根据自己的来更改奥       

        index  test.html test.htm;   

    }

}

运行的效果的截图:

这样就实现了Service Worker 与其他页面的通信,感兴趣的小伙伴可以一起来试一试奥.如果你出现报错的情况,要仔细检查自己的代码奥~

 结语

嗯,花了一番功夫终于总结完了,但是文章可能不够详尽,毕竟一个技术会有很多扩展和分支领域,很难一一介绍清楚,我写博客的水平也还有待提升,欢迎对这个有研究或者有兴趣或者发现文章有错误的地方的伙伴们和我交流,共同进步~~~.我的邮箱2510909248@qq.com.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,905评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,140评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,791评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,483评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,476评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,516评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,905评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,560评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,778评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,557评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,635评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,338评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,925评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,898评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,142评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,818评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,347评论 2 342