打开不同源的窗口并传递数据

背景

最近遇到一个需求:点击链接打开一个不同源的页面,并且需要传递数据到新页面

思考

  1. 打开页面 可以通过 window.open 打开,也可以通过点击<a>标签打开
  2. 传递数据 两个页面之间传递数据的方式有
方式 适用场景 局限性
通过 URL 参数传递
cookie 同源,发请求时需要带到请求头上 有大小限制(4K)
sessionStorage 1. 同源 2. 在 session 有效期内使用,不需要持久保存数据 3. 发请求时不需要带上 1. 通过复制窗口打开的页面,sessionStorage 不共用 2. 会话结束后,清除数据 3. 只能同源使用
localStorage 1. 同源 2. 需要持久保存数据 3. 需要跨页签共享 4. 发请求时不需要带上 1. 有大小限制 2. 只能同源使用
postMessage 可以跨域传递数据 不对来源进行限制时,有安全风险
MessageChannel 两个页面(可以不同源)之间通信;web-worker 中可用
BroadcastChannel 同源的不同浏览器窗口,Tab 页,frame 或者 iframe 下的不同文档之间相互通信;web-worker 中可用 只能用于同源页面
SharedWorker 两个页面之间通信;web-worker 中可用 只能用于同源页面:developer.mozilla.org/zh-CN/docs/...

由于需要跨域,所以可能的方案只有:通过 URL 参数传递,postMessage,MessageChannel

1. 通过 URL 传递数据

e.g.

A 页面:http://localhost:8080

B 页面:http://localhost:8081

A 页面打开 B 页面

window.open('http://localhost:8081?key=value')

优点 简单,直观
缺点 明文显示参数,不安全 URL 参数有长度限制
结论 能实现功能,但是不建议采用

2. 通过 postMessage 传递数据(不借助 MessageChannel)

source: http://localhost:8080 target: http://localhost:8081

通过 window.open 打开新页面

发送

  1. 通过 window.open 拿到 target 窗口的引用
  2. postMessage, 注意第二个参数 targetOrigin 要和 target 域完全一致
javascript 复制代码
const other = window.open("http://localhost:8081");
setTimeout(() => {
  other.postMessage(
    {
      csrf_token: "bearer 666",
    },
    "http://localhost:8081"
  );
}, 5000);

接收

  1. 用 onmessage 或者 addEventListener 监听 message 事件
  2. 通过 e.origin 获取到 source 的域名,进行判断
  3. 通过 e.data 获取到数据
javascript 复制代码
window.addEventListener(
  "message",
  (e) => {
    if (e.origin !== "http://localhost:8080") {
      return;
    }
    this.data = e.data;
  },
  false
);

结论

  1. 打开新页面后,延迟发送数据,造成等待,体验不好,且不够稳定(时延设置得过短,可能会偶现接收不到数据的问题)
  2. 能实现功能,但是不建议采用

3. postMessage+MessageChannel

本方式是对 postMessage 的优化,两个页面通过事件通信,避免手动设置延迟

source: http://localhost:8080 target: http://localhost:8081

  1. 通过 window.open 打开新页面
  2. 新页面打开后,向原页面发送消息(表示新页面已就绪,请求传输数据)
  3. 原页面接收到新页面发送的消息后,向新页面发送数据
  4. 新页面接收老页面发送的数据后,渲染到页面上

2 和 3 用顺序图表示如下:

细化

  1. 如何在新页面打开后才向原页面发送消息?

在新页面的 load 事件回调中发送消息 可以通过 window.onload 或者 window.addEventListener

  1. 新页面如何向原页面发送消息?

使用 window.opener.postMessage 函数

  1. MessageChannel 的 port1,port2 如何使用?

新页面使用 port1 发送第一条消息(就绪消息) 原页面使用 port1 发送第二条消息(数据消息) 新页面监听 port2 的 message 事件,拿到第二条消息

  1. 顺序图的 3 步如何实现?

window.opener.postMessage 传入第 3 个参数:port1 原页面监听 message 事件时,通过 e.ports 得到 port1,使用 port1 发送第二条消息(数据消息) 新页面监听 port2 的 message 事件,通过 e.data 拿到数据,渲染到页面上

细化后的逻辑如下

新页面打开后,在 load 事件回调中,创建 MessageChannel 对象,获取到 port1 和 port2。向原页面发送消息(表示新页面已就绪,请求传输数据)和 port1(作为 postMessage 的第 3 个参数 transfer) 原页面监听 message 事件,接收到新页面发送的消息后,向新页面发送数据 新页面监听 port2 的 message 事件,通过 e.data 拿到数据,渲染到页面上

实现代码如下

数据发送端

source: http://localhost:8080

javascript 复制代码
window.addEventListener("message", (e) => {
  if (e.origin !== "http://localhost:8081") {
    return;
  }

  if (e.origin !== "http://localhost:8081") {
    return;
  }

  const [port1] = e.ports;
  if (e.data === "8081 loaded") {
    port1.postMessage({
      foo: "bar",
    });
  }
});
数据接收端

target: http://localhost:8081

javascript 复制代码
window.addEventListener(
  "message",
  (e) => {
    if (e.origin !== "http://localhost:8080") {
      return;
    }

    this.data = e.data;
  },
  false
);

window.onload = () => {
  const { port1, port2 } = new MessageChannel();

  window.opener.postMessage("8081 loaded", "http://localhost:8080", [port1]);
  port2.onmessage = (e) => {
    this.data = e.data;
  };
};

注: port2 可以用 onmessage,也可以用 addEventListener('message',(e)=>{}) 如果用了后者,需要调用 port2.start() 如果用的 onmessage,会自动调用 port2.start()

port2 用addEventListener('message')来监听的代码如下

javascript 复制代码
port2.addEventListener("message", (e) => {
  this.data = e.data;
});
port2.start();

效果如下

发送的消息为

json 复制代码
{
  "foo": "bar"
}

结论

postMessage+MessageChannel 可以采用

相关推荐
echome88840 分钟前
JavaScript async/await 错误处理:别让 Promise 静默失败
开发语言·前端·javascript·promise
#麻辣小龙虾#2 小时前
网页布局css中flex+margin的组合布局技巧
开发语言·前端·javascript
Hello.Reader2 小时前
Three.js / Cesium 卡顿怎么办?把三维渲染搬到 GPU 服务器
服务器·开发语言·javascript
chi11302 小时前
【无标题】
前端·javascript·python
萝卜兔0083 小时前
PIKACHU_UnsafeFileUpload
javascript·php
程序员大阳15 小时前
副队长JavaScript教程(50)-DOM:网页开发的起点
javascript·dom·es·节点·概念·document·关系
香芋芋圆19 小时前
iD-editor 详解|OSM官方在线编辑器,基于D3.js的轻量地图编辑器
开发语言·javascript·编辑器
晗夜未央21 小时前
任务队列反复失败,先分清重试、阻塞和结果待核对
开发语言·javascript
jktantan1 天前
每个节点 41 字节:把一棵百万节点树拆进 14 个 typed array
前端·javascript·vue.js·算法·typescript
我命由我123451 天前
访客系统形式(有用户概念、无用户概念)
java·开发语言·前端·javascript·python·产品运营·产品经理