web端跨页面通信

202 阅读5分钟

前言

这篇文章的主旨是带你了解web端跨页面通信相关的一些知识,所以会有重复代码没有进行封装的情况

什么是跨页面

这里指的是在同一个浏览器中不同页签下的页面进行数据传递。主要包含两种情况: 同源[1]和非同源(也就是跨源)

同源通信

同源的情况下有两种方式可以实现跨页面通信
· 共享worker(SharedWorker)
· Broadcast Channel API

共享worker

多个页面通过共享同一个worker进行消息的相互通信

关于SharedWorker的相关使用可以参考我之前的文章[2]

兼容性

主流浏览器基本都支持

Broadcast Channel API

broadcast(广播)     channel(频道)

如何使用

// 初始化语法
// 需传入一个字符串作为创建频道的标识
const bc = new BroadcastChannel('xxx')

// 发送消息
bc.postMessage('你发的消息')
bc.postMessage({ msg: 'hello' })

// 接收消息
bc.onmessage = (event) => {
  console.log(event.data)
}

开始使用

首先新建一个发送端A.html,在这个文件中主要包含以下三点信息:

① 初始化/连接到广播频道

② 监听信息接收。若收到信息则验证数据的来源是否安全,验证后输出到控制台

③ 点击send按钮,发送一条广播信息

<!DOCTYPE html>
<html lang="en">
<head>
  <title>A</title>
</head>
<body>

  <h1>发送端 - A</h1>
  <hr>
  <button onclick="sendMessage()">send</button>

  <script>
    // 初始化广播频道实例
    const MyCommunicationChannel = new BroadcastChannel('my_secret_channel_007')
    // 唯一标识符,用作避免产生不必要的安全问题
    const UID = 'viavacos'

    MyCommunicationChannel.onmessage = (event) => {
      // 消息中未携带数据。直接跳出
      if(!event.data) return;

      const { uid, msg, from } = event.data
      // 消息中唯一标识符不正确。直接跳出
      if(uid !== UID) return;

      console.log(`收到来自${from}的消息:${msg}`)
    }

    // 初始化要发送的信息
    const sendData = { uid: UID, from: 'A', msg: 'Hello every body! Can you hear me?' }
    // 发送信息
    function sendMessage () {
      MyCommunicationChannel.postMessage(sendData)
    }
  </script>
  
</body>
</html>

接着定义两个接收端文件B.html和C.html

这两个文件中的内容几乎一致,除了名称

接收端主要做了以下三点:

① 初始化/连接到广播频道

② 监听信息接收。若收到信息则验证数据的来源是否安全,验证后以表格信息输出到控制台

③ 在接收到发送端的消息后回复一条消息

<!DOCTYPE html>
<html lang="en">
<head>
  <!-- 1.名称不同的地方 -->
  <title>B</title>
</head>
<body>
  <!-- 2.名称不同的地方 -->
  <h1>接收端 - B</h1>

  <script>
    const MyCommunicationChannel = new BroadcastChannel('my_secret_channel_007')
    const UID = 'viavacos'

    MyCommunicationChannel.onmessage = (event) => {
      if(!event.data) return;

      const { uid, from, msg } = event.data
      // 若唯一标识符不正确 或者 消息来源不是A。直接跳出
      if(uid !== UID || from !== 'A') return;

      // 以表格形式输出到控制台
      console.table({ uid, from, msg });

      // 3.名称不同的地方
      const sendData = { uid: UID, from: 'B', msg: 'I have received!' }
      MyCommunicationChannel.postMessage(sendData)
    }
  </script>
  
</body>
</html>

最后运行一下这两个文件或者以服务器托管形式(live server 或者 serve)打开,就能看到如下效果:

由发送端A发送一条消息后会接收到2条消息,分别是接收端B和C发送的回执

兼容性

目前还活着的浏览器都已经支持该特性

跨源通信

window.postMessage

这个API即可以实现【同源】通信,也可以实现【跨源】通信,这里主要介绍的是后者(代码都差不多,前者看代码中注释掉的那部分即可)

/*
 * 发送消息
 * @params message 你要发的信息
 * @params targetOrigin 目标的源信息
 */
window.postMessage(message, targetOrigin)

// 发送示例
window.postMessage('哈喽啊', 'http://127.0.0.1')


/*
 * 接收消息
 * 通过监听message来接收信息
 * event.data   信息中收到的数据
 * event.source 发送者的window实例
 * event.origin 发送者的源信息
 */
window.addEventListener('message', event => {
  console.log(event.data)
})

开始使用

首先创建一个发送端的A.html文件
内容主要包含以下五点:

① 点击按钮通过window.open打开接收端(页面B)
② 通过window.open的返回值拿到接收端的window实例
③ 通过实例发送一条带有uid的数据
④ 通过监听message事件来获取传递的信息 ⑤ 验证接收的数据安全后输出到控制台

<!DOCTYPE html>
<html lang="en">
<head>
  <title>A</title>
</head>
<body>
  <title>发送端 - A</title>
  <hr>
  <button onclick="openPageBAndThenSendMessage()">打开页面B并发送消息</button>

  <script>
    // 唯一标识符,随便定义就行
    const UID = 'viavacos'

    // 打开页面B并发送消息
    function openPageBAndThenSendMessage () {
      // 接收端的源信息
      // const targetOrigin = 'http://127.0.0.1:5500' // 源信息一样就是【同源】通信
      const targetOrigin = 'http://127.0.0.1:5501' // 源信息不一样就是【跨源】通信
      // 新页面B的window对象
      const otherWin = window.open(targetOrigin + '/B.html')
      // 要发送的消息 uid-唯一标识符(随便定义即可)  msg-你要发送的信息
      const message = { uid: UID, msg: 'Hi~' }

      // 【同源】当目标页面加载完成后,发送消息
      // otherWin.onload = () => {
      //   otherWin.postMessage(message, targetOrigin)
      // }

      // 【跨源】设置一个延迟(设置的时间需要保证目标页面加载完成)
      // 这里为什么不用onload事件呢?结尾告诉你
      setTimeout(() => {
        otherWin.postMessage(message, targetOrigin)
      }, 100);
    }

    // 监听信息接收
    window.addEventListener('message', event => {
      if(!event.data) return;

      const { uid } = event.data
      if(uid !== UID) return;

      console.log(1008600, event.data);
    })
  </script>
</body>
</html>

接着创建个接收端B.html,这里主要包含以下点信息

① 监听消息接收
② 接收到消息后验证数据是否有效并输出到控制台中
③ 通过发送者携带的window实例和源信息回复一条消息

<!DOCTYPE html>
<html lang="en">
<head>
  <title>B</title>
</head>
<body>

  <h1>接收端 - B</h1>

  <script>
    // 唯一标识符,随便定义就行
    const UID = 'viavacos'

    // 监听信息接收
    window.addEventListener('message', event => {
      // 接收的信息中没有数据,跳出
      if(!event.data) return;

      const { uid, msg } = event.data
      // 数据校验不通过,跳出
      if(uid !== UID) return;

      console.log(1008600, event.data);

      const feedbackMessage = { uid: UID, msg: '我收到辣!!!' }
      // 通过消息的来源回复一条消息
      event.source.postMessage(feedbackMessage, event.origin)
    })
  </script>
  
</body>
</html>

最后本地跑个服务器去打开这俩文件并得到两个不同源的地址,这里是案例中的源信息:
http://127.0.0.1:5500(发送端)
http://127.0.0.1:5501(接收端)
并得到下图中的结果

PS: 使用不同的工具会得到不同的源信息

缺点

这个API必须拿到接收方的window对象,然后以对方的·window对象给自己发送信息,因此这个方法就比较有局限性,一般常见的场景主要有这两种:
· 和通过window.open打开的页面进行通信
· 父页面和被用iframe嵌套的子页面进行通信

兼容性

大多数的浏览器基本上也都是支持的

总结

1.跨页面通信分为同源和跨源两种

  • 同源通信可以通过SharedWorkerBroadcastChannel实现
  • 跨源通信可以通过window.postMessage实现

2.SharedWorker通过共享同一个worker进行数据的发送和接收(引入同一个SharedWorker初始化文件)

3.BroadcastChannel 通过使用相同的字符串建立/进入相同的广播频道,以此进行数据传递

4.window.postMessage利用window.open可以返回被打开页面的window对象进行数据传递

5.无论是在哪种场景下使用哪个方式进行数据传递,都应该注意下数据的安全性;比如文章中通过uid对数据交互的另一方进行简单的身份鉴定,更严格一些的话可以通过添加白名单的形式去判断当前信息来源是否可信

// 域名白名单的简单示例

<script>
  // 定义白名单(只有在这里定义过的地址是可信的)
  const whiteList = ['https://www.bilibili.com', 'https://juejin.cn', 'http://192.168.111:8081'];
  window.addEventListener('message', event => {
    // 数据来源不可信
    if(!whiteList.includes(event.origin)) return;

    // 进行其它的操作
    // ...
  })
</script>

callback

// 【同源】当目标页面加载完成后,发送消息
// otherWin.onload = () => {
//   otherWin.postMessage(message, targetOrigin)
// }

// 【跨源】设置一个延迟(设置的时间需要保证目标页面加载完成)
// 这里为什么不用onload事件呢?
setTimeout(() => {
  otherWin.postMessage(message, targetOrigin)
}, 100);


/*
 * 这里也是因为受到了同源策略的影响
 * 通过window.open打开的页面若是同源的,则通过监听
 * 返回的window对象中的onload事件会被触发,反之则不会触发
 */

[1] developer.mozilla.org/zh-TW/docs/…
[2] mp.weixin.qq.com/s/0C7DLXF6r…

示例代码: github.com/ViavaCos/cr…

PS: 该文章已同步在公众号[时不荒]上发布