Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

理解 MessageChannel #6

Open
luokuning opened this issue Jun 1, 2016 · 0 comments
Open

理解 MessageChannel #6

luokuning opened this issue Jun 1, 2016 · 0 comments
Labels

Comments

@luokuning
Copy link
Owner

刚刚无意之中在网上看到 web worker 这个词,瞬间跌入了回忆的深渊,什么 serviceWorkerwalking dead、亲爱的长者 乱七八糟一齐涌了出来,就是没有 web worker,最后颓废的发现,原来关于 worker 相关的东西自己已经忘干净了。

不过隐约记得这东西用起来不很复杂,于是果断上 MDN 看看文档,但是又无意中看到 MessageChannel 这个词,很明显幼小的心灵又被一个陌生的词汇狠狠的鞭笞了一下。忍不住点进去看了一下关于 MessageChannel 的资料,发现这行代码 (来源):

otherWindow.postMessage('Hello from the main page!', '*', [channel.port2]);

蛤?原来 postMessage 方法可以接收第 3 个参数?那请问第三个参数是什么意思?文章里说的 MessgaePort 对象又是什么?想到这么多问题自己还完全摸不着边,而且问题之间环环相扣,于是仔细深究了一番,下面是一些笔记。

什么是 MessageChannel ?

首先,MessageChannel 是一个构造函数,创建一对相互连接的 MessagePort 对象。

var mc=new MessageChannel;
mc.port1.onmessage=function(e){console.log("port1:"+e.data);};
mc.port2.onmessage=function(e){console.log("port2:"+e.data);};
mc.port1.postMessage("1"); //会在port2的message事件收到
mc.port2.postMessage("2"); //会在port1的message事件收到

(这段代码的来源)

上面说的一对相互连接的 MessagePort 对象分别是 mc.port1mc.port2,他们可以相互给对方发送消息,并且处理接收到的消息。这就是 MessageChannel 最基本的用法,很简单,但是看起来很无用。

postMessage 很好用

我们知道 postMessage 可以用于 worker 和跨文档消息传递机制,用起来也很简单,只要记住 postMessage 是”自己给自己发消息“,我们以后者为例:

// index.html
var iframe = document.querySelector('iframe')
iframe.onload = () => {
  let w = iframe.contentWindow

  w.onmessage = (e) => {
    console.log(e.data)
  }
  w.postMessage('initialize', '*')
}

// iframe.html
onmessage = (e) => {
  console.log(e.data)
  window.postMessage(`Received message: ${e.data}`)
}

上面这段代码逻辑非常简单,iframe 自己给自己发消息并处理接收到的消息,但是可能不好理解的就是所谓的 “自己给自己发消息”。在我们正常的认知中,发消息通常都是一方发给另外一方,哪有自己给自己发消息的 (自言自语除外)。而且因为自发消息的特性,在不同的上下文中 (不同的页面或者 worker 与 main thread 间),postMessageonmessage 在写法上看起来又不像是 ”自己给自己发“:在 index.html 中写法是 iframe.contentWindow.postMessage,在 iframe.html 中写法又是 window.postMessage。总之,如果不习惯的话从文字上看起来会觉得这种写法有些别扭,不过好在我们还有另一种写法。

MessageChannel 让通信变得更简单

上面说到 MessageChannel 构造函数会创建一对相互关联的 MessagePort 对象,怎么理解 MessagePort 对象呢?可以把一个 MessagePort 对象当成消息传递的管道,我们可以通过这两个对象来进行消息的传递,想象一下现实生活中的两个人通过手机打电话,一个 MessagePort 对象就是一个手机。

MessageChannel 其实就更好理解了,不再是自己给自己发消息,而是一方发给另一方,也即是 mc.port1mc.port2 相互传递处理消息。

但是怎么利用 MessageChannel 来让我们现有的消息传递机制更加简单清晰呢?答案正是我们上面说的 window.postMessage 方法的第三个参数 (MessagePort.postMessage 里是第二个参数)。

这个参数是可选的,而且如果有的话必须是 Array/ArrayLike,表示 “跟随信息一起传送的可转让对象的序列”,意思就是把这些可转让对象的所有权转让给目的上下文 (注意只有 MessagePortArrayBuffer 对象可以被转移)

什么意思呢?看文章里的第一行代码:

otherWindow.postMessage('Hello from the main page!', '*', [channel.port2]);

这里 channel.port2 就被转移到 otherWindow 上下文中去了,也就是现在 channel.port2 能够被 otherWindow 上下文访问到了。不过这样有什么用呢?我们不还是用了 postMessage 方法吗?

其实很好理解,想像一下,现在你跟你的一个小伙伴分隔两地,通信是通过邮政寄信的方式,很不方便,但是某天你突然得到两个电话,然后通过邮政发给你的小伙伴一个,这样等小伙伴收到电话你们就能愉快的打电话了。翻译过来就是:通过 postMessage 把其中一个 MessagePort 对象传递给需要通信的一方,然后双方就可以通过 MessagePort 对象相互通信。

好了就说这么多,下面直接上完整的代码,很好理解:

// index.html
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title></title>
</head>
<body>
    <iframe src="iframe.html" frameborder="0"></iframe>
    <script>
        var channel = new MessageChannel
        var port = channel.port1

        var iframe = document.querySelector('iframe')
        iframe.onload = () => {
          iframe.contentWindow.postMessage('initialize', '*', [channel.port2])
        }

        port.addEventListener('message', (e) => {
          console.log(e.data, e)
        }, false)

        // 因为我们用的是 addEventListener 而不是 onmessage,
        // 所以需要调用 port.start 方法
        // 见: https://developer.mozilla.org/en-US/docs/Web/API/MessagePort
        port.start()        
    </script>
</body>
</html>

// iframe.html
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title></title>
</head>
<body>
    <script>
      onmessage = (e) => {
        // index.html 传递过来的 MessagePort 对象
        // 通过 e.ports[0] 获取
        var port = e.ports[0]

        // 现在可以愉快的通过 port
        // 进行消息传递
        port.onmessage = (e) => {
          console.log('from iframe: ' + e.data)
        }
        port.postMessage('sad')       
      }
    </script>   
</body>
</html>

总结

MessageChannel 是基本的双向信息传输管道,可以把它想象成 window.postMessage/window.onmessage 的另一种更加清晰的替代方案。最后要说的一点就是,sharedWorker 默认使用了 MessagePort 进行消息传递,有兴趣的同学可以自行探索。

参考链接:

MDN
7 Things You Need To Know About Web Workers
Messing with MessageChannel

@luokuning luokuning modified the milestone: testt Jun 16, 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant