嗦嗦postMessage和webSocket

2,325 阅读6分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第13天,点击查看活动详情

  • Hello,这里是mouche,当然你也可以叫我某车,反正大家都爱这么叫😁
  • 我们在上一篇小白也能搞懂的JSONP和CORS跨域方案已经说过两种跨域方案了,这一篇就再继续讲讲postMessagewebsocket这两种方案,它们也能算得上是跨域方案🤓

一、postMessage

✍是什么

  • window.postMessage() 方法提供了一种受控机制来规避此限制,只要正确的使用,这种方法就很安全
  • 听听MDN的解释:一个窗口可以获得对另一个窗口的引用,然后在窗口上调用 targetWindow.postMessage() 方法分发一个 MessageEvent 消息。接收消息的窗口可以根据需要自由处理此事件

✍语法

targetWindow.postMessage(message, targetOrigin, [transfer]);
  • targetWindow: 其他窗口的一个引用,比如 iframecontentWindow 属性、执行window.open返回的窗口对象、或者是命名过或数值索引的window.frames (en-US)
  • message: 将要发送到其他 window 的数据。它将会被结构化克隆算法 (en-US)序列化。这意味着你可以不受什么限制的将数据对象安全的传送给目标窗口而无需自己序列化
  • targetOrigin: 通过窗口的 origin 属性来指定哪些窗口能接收到消息事件,其值可以是字符串*(表示无限制)或者一个 URI。在发送消息的时候,如果目标窗口的协议、主机地址或端口这三者的任意一项不匹配它提供的值,那么消息就不会被发送;只有三者完全匹配,消息才会被发送。这个机制用来控制消息可以发送到哪些窗口
  • transfer 可选: 是一串和 message 同时传递的 Transferable 对象。这些对象的所有权将被转移给消息的接收方,而发送一方将不再保有所有权。

通过以上叙述我们能够了解到它的作用就是可以安全地给目标窗口发送自定义的信息

✍怎么用

有信息的发送,自然也要有信息的接收,我们可以采用addEventLister,监听message事件: 该事件接收到消息时触发

  • 我们先在同一个窗口对其进行测试(在Origin值为http://127.0.0.1:5500/的页面打开)
const data = {
  name : '某车',
  like:  '前端'
}
//传入的message为data,targetOrigin为http://127.0.0.1:5500/
window.postMessage(data, 'http://127.0.0.1:5500/');
window.addEventListener('message',(event)=>{
//监听该回调事件并打印
  console.log(event);
})
  • 我们看一下打印台,可以看到里面的data正是我们传入的数据,origin是发送消息的源,source为发送消息的窗口引用(后面这两个,用于我们回发消息,实现消息互通)

image.png

✍如何跨域

上述我们提到了其他窗口的引用可以是iframecontentWindow属性,也可以是window.open的返回值那么我们就都来试试看

iframe + postMessage
  • 这里是使用3300端口父页面向内嵌子页面3301端口发送消息
//a.html
 <!-- 使用iframe,src指向3301端口 -->
<iframe src="http://127.0.0.1:3301/b.html" id="frame" onload="load()"></iframe>
<script>
const data = {
  name : '某车',
  like:  '前端'
}
const load = function(){
  //负责发布消息
  let frame = document.getElementById('frame'); 
  const targetWindow = frame.contentWindow;//得到目标窗口的引用
  targetWindow.postMessage(data, 'http://127.0.0.1:3301'); //发送新消息
  //也监听信息
  window.onmessage = function(event) {
    console.log(event.data)
  }
}
</script>
  • 这里记得在onload事件里面去触发,否则会报如下错误

image.png

  • 3301端口监听message事件,然后通过event.source得到3300端口页面的引用,event.origin获取3300端口的源,然后可以回发消息
    //b.html
    window.addEventListener('message',(event)=>{
      console.log(event.data);
      event.source.postMessage('我可以回发消息给你', event.origin);
    })
  • 3300端口页面的打印 (图片端口号打错了,懒得修改了..) image.png
window.open()+postMessage
  • 先认知一下window.open()语法

    window.open(url, [name], [configuration])
    
    • url:为要新打开页面的url
    • name:为新打开窗口的名字,可以通过此名字获取该窗口对象
    • configuration:为新打开窗口的一些配置项,比如是否有菜单栏、滚动条、长高等等信息
  • 还是上面的思路,我们让3300端口3301端口发送信息

   //a.html
  <button class="openWindow">打开窗口</button>
  <script>
    const btn = document.querySelector('.openWindow');
    const data = {
      name : '某车',
      like:  '前端'
    }
    //点击之后,执行window.open()
    btn.addEventListener('click', ()=>{
     const targetWindow =  window.open('http://127.0.0.1:3301/b.html', '3001端口'); //拿到目标窗口的引用
     setTimeout(()=>{
      targetWindow.postMessage(data, 'http://127.0.0.1:3301/'); //发送数据
     },1000)
    
    })
    //同时监听message事件
    window.addEventListener('message',(event)=>{
      console.log(event.data);
    })
  </script>
  • 3301端口监听message事件,并且回发信息
    //b.html
    window.addEventListener('message',(event)=>{
      console.log(event.data);
      event.source.postMessage('我可以回发消息给你', event.origin);
    })
  • 看一下控制台结果
    • 3301端口打印如下 image.png

    • 3300端口打印如下

      image.png

    经评论区大佬提醒,直接延时1秒钟发信息不够严谨,如果子页面加载比较缓慢那么则无法成功发送,可以选择让子页面加载完成后主动发给父页面,然后再由父页面接到信息后再回发给子页面,这样能保证互通

✍兼容性

  • 还是在IE有兼容问题 image.png

小结:postMessage()方法允许来自不同源的脚本进行有限的通信,只要能获取到源和窗口对象就可以实现跨域消息传递

二、webSocket

  • webSocket是一种在单个TCP连接上进行全双工通信的协议,它实现了浏览器与服务器全双工通信,能更好的节省服务器资源和带宽并达到实时通讯的目的
  • WebSocketHTTP都是应用层协议,都基于 TCP 协议
  • 只需要一次握手(使用HTTP协议)客户端和服务端就可以建立持久性的链接,进行双向数据传输

✍出现的原因

  • 需要实现“聊天室”,“消息推送”,“实时动态”等功能
  • 曾经的方案:
    • 短轮询;每隔一段时间就询问一次服务器是否有新的消息,缺点就是有一定的延迟,浪费资源
    • 长轮询:客户端发送请求后如果数据没有更新的话服务器就先将其挂起,有新消息则传回,等传回后又重新发起请求等待数据更新,缺点就是服务器需要保持大量的连接

✍连接流程

  • 客户端先用Upgrade:Websocket请求头的HTTP请求,向服务器发起握手请求
Connnection:Upgrade  //表示要升级协议
Upgrade: websocket //表示要升级为websocket协议
Sec-webSocket-Version:13 //版本
  • 握手成功后,即升级协议,两端就可以相互传递消息了

✍兼容性

image.png

为什么说它是解决跨域的方案:因为它本身就不受同源策略的限制,客户端可以和任意服务器之间进行通信

跨域小结

  • JSONP因为仅支持get请求,淘汰无人问津是迟早的事情
  • CORS支持所有类型的HTTP请求,是跨域HTTP请求的根本解决方案,但是我们上一篇也提了,还是有IE兼容问题
  • postMessage方法允许来自不同源的脚本进行有限的通信,只要能获取到源和窗口对象就可以实现跨域消息传递
  • websocket本身不受同源策略的限制,可以在不同源间进行通信
  • 最后还有nginx反向代理,它可以说基本没啥毛病?不过我不怎么了解它就不介绍啦🤡