Skip to content
本页目录

两个窗口如何实现通信 - 掘金

在工作中就出现的这种问题,开发的产品的页面需要嵌入到公司中其他产品中,因为是不同的产品,属于不同的系统,最简单的方式就是iframe和window.open()的方式,我选择的是window.open()的方式,因为实现的效果就是点击某个按钮才显示该页面,使用window.open()的方式对于布局都比较好控制,这时就有个问题了。因为开发产品页面直接需要获取该用户在其他产品时的用户信息和产品信息,就涉及到了如何传递信息的问题了

window.open()的参数介绍

对于window.open()一般可以设置三个参数,这里就简单介绍了,如果需要更深入的了解就请自己查找资料

  • 第一个参数,打开新窗口的url地址

  • 第二个参数,给新窗口的名字(name),并不是新窗口在窗口显示的title,在窗口下,通过window.name的方式拿到

    • 这里也可以设置_self(在旧窗口打开新窗口),_blank(重新打开新窗口,默认就是该模式)
  • 第三个参数,设置打开新窗口的宽高等

    • 一般设置'left=100,top=100,width=400,height=400',四个参数,其他参数可能有浏览器兼容问题
      

返回值——新窗口的window引用

这时就有个疑问了,在设置第二参数时,我又想设置_self又想给新窗口设置name怎么办?

可以使用如下方式:

js
var targetWindow = window.open('', '_self')
targetWindow.name = 'demo'
targetWindow.location.href = 'http://localhost:8081/'

你会想这样写这么麻烦,下面这么写不就很简单啊

js
var targetWindow = window.open('http://localhost:8081/', '_self')
targetWindow.name = 'demo'

根据MDN中的介绍(Window:open() 方法 - Web API 接口参考 | MDN (mozilla.org))

URL 的实际获取是延迟进行的,并在当前脚本块执行完毕后开始。窗口创建和引用资源的加载是异步进行的。

在第一种方法中并没有url的加载,所以窗口是已经创建了,再给这个窗口设置的name。第二种方法有url的异步加载,该窗口并没有被创建好,就给这个窗口设置name,自然就无效了。

1.使用url携带参数的形式

可能最容易想到的方式就是使用url上携带参数的形式了,但是这种方式对于传递多种数据是不方便的,而且安全性也是个问题

2.使用postMessage的方式(使用环境:Vue)

通过查找资料发现了这种比较有意思的方式,这种方式竟然对于跨域也是可以解决的,这不正合我意。

postMessage常用参数

  • 第一个参数,就是传递的消息message,可以使用字符串,信息过多可以使用JSON.stringify()的方式
  • 第二个参数,就是发送的url地址,也可以使用*来代替,但是不安全

发送方使用postMessage,接收方绑定message事件

js
window.addEventListener("message",(e) => {
  // 判断是否是旧窗口发过来的,这个if判断是必须的,然后会多接收一些不想接收的消息,就是信息还没有发送过来,绑定message事件会有默认信息
  if (e.origin !== "http://localhost:8080") return;
  // e.data——接收到的信息
  // e.origin——发送发的url地址,如果没有if判断,就会返回接收方的url(默认信息)
  // e.source——发送方的window引用,如果没有if判断,就会返回接收方的window(默认信息)
  // e.origin和e.source结合可以让接收方向发送方发送信息,从而达到双向通信
    console.log(e.data)
 })

我就直接介绍跨域的方式如何使用,对于同源的方式也可以使用该方式,也有其他方式。

我想要的,

旧窗口把信息传给新窗口

旧窗口

vue
// http://localhost:8080/


<template>
 <div id="app">
  <button @click="open">打开新窗口</button>
 </div>
</template>

export default {
 name: 'App',
 data () {
  this.targetWindow = null
  return {
   }
  },
 methods: {
  open() {
   this.targetWindow = window.open('http://localhost:8081/', '_blank', 'left=100,top=100,width=400,height=400')
   // 为什么加定时器,主要是为了防止window.open()异步加载,页面没有加载出来,就把消息发送出去了,有更好的方式也可以使用其他方式
   setTimeout(() => {
    this.targetWindow.postMessage('旧窗口向新窗口发送的消息', 'http://localhost:8081/')
    }, 1000)
   }
  }
}
</script>

新窗口

vue
// http://localhost:8081/

<template>
 <div id="app">
  新窗口
 </div>
</template>

export default {
 name: 'App',
 mounted() {
  window.addEventListener("message",(e) => {
   if (e.origin !== "http://localhost:8080") return;
    //发送方发送的信息
     console.log(e.data)
   })
  }
}

旧窗口可以向新窗口发信息,新窗口也可以发送信息给旧窗口

旧窗口

vue
// http://localhost:8080/

<template>
 <div id="app">
  <button @click="open">打开新窗口</button>
  <button @click="sendNew">向新窗口传消息</button>
  旧窗口接收到的信息:{{ text }}
 </div>
</template>

export default {
 name: 'App',
 data () {
  this.targetWindow = null
  return {
   text: ''
   }
  },
 mounted() {
  window.addEventListener('message',(e) => {
   if (e.origin !== "http://localhost:8081/") return;
   console.log(e.data)
   this.text += e.data
   })
  },
 methods: {
  open() {
   this.targetWindow = window.open('http://192.168.3.76:8081/', '_blank', 'left=100,top=100,width=400,height=400')
   setTimeout(() => {
    this.targetWindow.postMessage('旧窗口向新窗口发送的消息', 'http://192.168.3.76:8081/')
    }, 1000)
   },
  sendNew() {
   this.targetWindow.postMessage('旧窗口通过按钮发送给新窗口的消息', 'http://localhost:8081/')
   }
  }
}
</script>

新窗口

vue
//http://localhost:8081/

<template>
 <div id="app">
  新窗口
  <button @click="sendOld">向旧窗口发送消息</button>
  <!-- 接收旧窗口发送的消息 -->
  接收旧窗口发送的消息:{{ text }}
 </div>
</template>

export default {
 name: 'App',
 components: {
  
  },
 data() {
  this.oldWindow = null
  this.oldOrigin = null
  return {
   text: ''
   }
  },
 mounted() {
  window.addEventListener("message",(e) => {
   if (e.origin !== "http://localhost:8080") return;
   this.text += e.data
   this.oldWindow = e.source
   this.oldOrigin = e.origin
   })
  console.log(window.name)
  },
 methods: {
  sendOld() {
   this.oldWindow.postMessage('新窗口通过按钮给旧窗口发送的消息', this.oldOrigin)
   }
  }
}

看到这里你可能发现有段代码很怪,为什么有些数据声明在return上面的,这么做是因为让这个数据失去响应式,如果声明在return内是有响应式的,这时this.targetWindow = window.open()在控制台会出现跨域的报错。原因是因为给它赋值是打开子窗口的window,赋值会就会监听targetWindow,就相当于在父窗口去对子窗口做操作,就造成了跨域的问题。

3.使用window.name的方式传递信息

利用window.open()的第二个参数,把信息传给新窗口

旧窗口

js
window.open('http://localhost:8081/','要传递的信息')

新窗口

console.log(window.name)

4.创建频道对象 BroadcastChannel

1、创建一个频道对象:const setChannel = new BroadcastChannel('demos'); 2、用postMessage和onMessage进行通信

BroadcastChannel API 允许您发送和接收在同一源(同一协议,主机和端口)的不同文档(例如,来自同一应用的不同选项卡或窗口)之间发送消息。

这是如何使用 BroadcastChannel API 的一个简单例子:

javascript
// 创建一个名为 'my_channel' 的新广播频道
const bc = new BroadcastChannel('my_channel');

// 监听 'message' 事件以接收消息
bc.onmessage = function (event) {
  console.log(event.data);
};

// 发送一条消息
bc.postMessage('This is a test message.');

在这个例子中,我们首先使用 new BroadcastChannel() 构造函数创建了一个新的广播频道。我们给这个频道命名为 'my_channel'。

然后,我们设置 onmessage 事件处理函数来监听 'message' 事件。每当我们的频道接收到一条消息时,这个函数就会被调用,事件对象将被传递给该函数。我们可以通过 event.data 访问接收到的消息。

最后,我们使用 postMessage() 方法发送一条消息。这条消息将被所有监听 'my_channel' 频道的文档接收。

需要注意的是,BroadcastChannel API 只在安全上下文(HTTPS,localhost,或者文件系统)中可用,并且在某些浏览器中可能不被支持。在使用之前,建议检查浏览器的兼容性。

总结

如果就是旧窗口传递信息给新窗口,不需要新窗口给旧窗口传递信息,且只传一次,三种方式都可以,推荐使用第三种方式

如果想实现两个窗口通信的话,就使用第二种方式