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

背景

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

思考

  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 可以采用

相关推荐
Martin -Tang1 小时前
Vue 3 中,ref 和 reactive的区别
前端·javascript·vue.js
FakeOccupational3 小时前
nodejs 020: React语法规则 props和state
前端·javascript·react.js
放逐者-保持本心,方可放逐3 小时前
react 组件应用
开发语言·前端·javascript·react.js·前端框架
曹天骄4 小时前
next中服务端组件共享接口数据
前端·javascript·react.js
郝晨妤5 小时前
鸿蒙ArkTS和TS有什么区别?
前端·javascript·typescript·鸿蒙
喝旺仔la6 小时前
vue的样式知识点
前端·javascript·vue.js
别忘了微笑_cuicui6 小时前
elementUI中2个日期组件实现开始时间、结束时间(禁用日期面板、控制开始时间不能超过结束时间的时分秒)实现方案
前端·javascript·elementui
尝尝你的优乐美6 小时前
vue3.0中h函数的简单使用
前端·javascript·vue.js
windy1a6 小时前
【C语言】js写一个冒泡顺序
javascript
会发光的猪。7 小时前
如何使用脚手架创建一个若依框架vue3+setup+js+vite的项目详细教程
前端·javascript·vue.js·前端框架