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

背景

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

思考

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

相关推荐
一颗花生米。2 小时前
深入理解JavaScript 的原型继承
java·开发语言·javascript·原型模式
学习使我快乐012 小时前
JS进阶 3——深入面向对象、原型
开发语言·前端·javascript
bobostudio19952 小时前
TypeScript 设计模式之【策略模式】
前端·javascript·设计模式·typescript·策略模式
勿语&3 小时前
Element-UI Plus 暗黑主题切换及自定义主题色
开发语言·javascript·ui
一路向前的月光8 小时前
Vue2中的监听和计算属性的区别
前端·javascript·vue.js
长路 ㅤ   8 小时前
vue-live2d看板娘集成方案设计使用教程
前端·javascript·vue.js·live2d
Fan_web8 小时前
jQuery——事件委托
开发语言·前端·javascript·css·jquery
Jiaberrr9 小时前
Element UI教程:如何将Radio单选框的圆框改为方框
前端·javascript·vue.js·ui·elementui
安冬的码畜日常11 小时前
【D3.js in Action 3 精译_029】3.5 给 D3 条形图加注图表标签(上)
开发语言·前端·javascript·信息可视化·数据可视化·d3.js
太阳花ˉ11 小时前
html+css+js实现step进度条效果
javascript·css·html