进阶篇:Web跨页面通信
Web 跨页面通信是指在浏览器中,不同页面间的信息传递。再详细些划分,可分两类:在同一窗口,一个页面跳转到另一个页面时的通信,如:a 标签或者 location.href 跳转等;在不同窗口打开的多个页面间的通信,如:调用 window.open() 打开多个页面、在浏览器手动输入地址打开多个页面等。
另外,由于浏览器同源策略的存在,通信又可以分为:同源页面通信,非同源页面通信。
本文总结了页面间通信的技术,包括非同源页面间的通信。文章最后还分析了如何在不同场景选择不同的技术方案。
BroadCast Channel
BroadcastChannel 表示一个命名频道,给定来源的任何浏览上下文都可以订阅该频道。它允许相同来源的不同文档(在不同的窗口、选项卡、框架或 iframe 中)之间进行通信。消息通过监听频道的对象上触发的事件进行广播。
首先,调用 BroadcastChannel 构造函数创建一个广播频道,该构造函数接受一个参数:频道名称。如果它是第一个连接到该广播频道名称的,则创建基础频道,并把它连接到底层的频道。
const BC = new BroadcastChannel('zhao')
注意: 所有需要使用该频道的页面都必须用同一名称创建 BroadcastChannel 实例。如果该名称的频道已存在,则不会创建新频道,直接连接已有的底层频道。
然后,各个页面可以通过 onmessage 来监听被广播的消息:
BC.onmessage = function (e) {
console.log(`[频道@${e.target.name}]接收:${e.data}`)
}
// 或者
BC.addEventListener('message', (e) => {
console.log(`[频道@${e.target.name}]接收:${e.data}`)
})
最后,在页面上调用 BroadcastChannel 实例的 postMessage 方法,可将任何类型的消息发送到所有监听同一频道的页面:
BC.postMessage('Hello, This is Page A!')
BC.postMessage({
message: 'Hello, This is Page A!',
form: 'pageA'
})
调用 BroadcastChannel 实例的 close 方法关闭频道,断开对象与底层频道的连接,并允许它最终被垃圾回收:
BC.close()
注意: 当前页面关闭频道,浏览器只是回收了当前页面的 BroadcastChannel 实例对象,底层频道还是存在的,其他页面仍可以广播或接收频道信息。
关闭频道后,调用 postMessage 方法会报错:
VM358:1 Uncaught DOMException: Failed to execute 'postMessage' on 'BroadcastChannel': Channel is closed
at <anonymous>:1:4
BroadcastChannel DEMO Page A.html?type=1)
BroadcastChannel DEMO Page B.html?type=1)
BroadcastChannel DEMO Page C.html?type=1)
注意 BroadCastChannel 的兼容性: 目前(2021.12),Edge、Firefox、Chrome、Opera 以及大多安卓移动端浏览器都已经支持,IE 6~11、Safari@15.2(包括IOS版本)及以下的不支持。
Service Worker
Service workers 本质上充当 Web 应用程序、浏览器与网络(可用时)之间的代理服务器。这个 API 旨在创建有效的离线体验,它会拦截网络请求并根据网络是否可用来采取适当的动作、更新来自服务器的的资源。它还提供入口以推送通知和访问后台同步 API。
将 Service Worker 作为消息的处理中心(中央站),它可以向作用域下的所有打开的页面发送消息,实现多页面之间的广播通信。
首先,注册 Service Worker:
navigator.serviceWorker.register('sw.js').then(() => {
console.log('ServiceWorker 注册成功')
})
其次,在 sw.js 添加 Service Worker 监听 message事件,接收所有 Service Worker 进程作用域下(即 sw.js 所在路径下)的 client(即当前域打开的页面)发送的信息。然后通过 self.clients.matchAll() 获取该作用域下的所有 client,再通过调用每个 client 的 postMessage 方法,向对应页面发送消息。这样,Service Worker 统一接收所有 client 的信息,再由各个 client 向各自页面发送信息,实现广播通信。
self.addEventListener('message', (event) => {
console.log(`[Client]接收:${event.data}`)
event.waitUntil(
self.clients.matchAll().then((clients) => {
if (!clients || !clients.length) {
return
}
clients.forEach((client) => {
client.postMessage(event.data)
})
})
)
})
然后,在页面监听 navigator.serviceWorker 对象的 message 事件,接收 ServiceWorker 发送的消息:
navigator.serviceWorker.addEventListener('message', (e) => {
console.log(`[ServiceWorker]接收:${e.data}`)
})
最后,在页面调用 navigator.serviceWorker 对象的 postMessage 方法广播消息:
navigator.serviceWorker.controller.postMessage('Hello, This is Page A!')
ServiceWorker DEMO Page A.html?type=2)
ServiceWorker DEMO Page B.html?type=2)
ServiceWorker DEMO Page C.html?type=2)
注意 ServiceWorker 的兼容性: 比 BroadcastChannel 好,目前(2021.12),除 IE 6~11,其他浏览器的新版本基本都支持。
LocalStorage
LocalStorage 是一种将数据缓存在浏览器中的技术。LocalStorage 与 SessionStorage 类似,但其区别在于:LocalStorage 缓存数据可以长期保留,直到手动清 除;而 SessionStorage 缓存数据会在当前会话结束——也就是说,当页面被关闭时清除 。
当 LocalStorage 变化时,会触发 storage 事件。利用这个特性,我们可以在发送消息时,把消息写入到某个 LocalStorage 中;然后在各个页面内,通过监听 storage 事件即可收到通知。
首先,在各页面监听 storage 事件:
window.addEventListener('storage', (e) => {
console.log(`[LocalStorage]接收:key-${e.key},新数据-${e.newValue},老数据-${e.oldValue}`)
})
然后,当某个页面需要广播消息时,调用 setItem 方法:
window.localStorage.setItem('msg', 'Hello, This is Page A!')
需要注意几点:
对 Storage 对象进行任何修改,都会在文档上触发
storage事件。如:setItem()、clear()、removeItem()等方法。setItem()方法只能存储 String 类型,对象类型需要JSON.stringify()方法字符串化。只有 Storage 对象有修改才会触发
storage事件。如:调用setItem()方法设置的新数据与老数据相同时,storage事件不会触发:// 连续两次调用 setItem 方法,设置同一值,只会触发一次 storage 事件。 window.localStorage.clear("msg") window.localStorage.setItem('msg', 'Hello, This is Page A!') window.localStorage.setItem('msg', 'Hello, This is Page A!') // 通常我们可以在数据中加一个时间戳。 window.localStorage.setItem('msg', `Hello, This is Page A!-${new Date().getTime()}`)Storage 对象的修改不会触发当前页面的
storage事件。
LocalStorage DEMO Page A.html?type=3)
LocalStorage DEMO Page B.html?type=3)
LocalStorage DEMO Page C.html?type=3)
Shared Worker
Shared Worker 是 Worker 家族的另一个成员。普通的 Worker 之间是独立运行、数据互不相通;而多个页面注册的 Shared Worker 则可以实现同源页面的数据共享。它可以从多个浏览上下文访问,例如多个窗口、iframe 甚至 Worker。
首先,在页面中创建一个 SharedWorker 实例:
// 第二个参数是 Shared Worker 名称,也可以留空
const sharedWorker = new SharedWorker('shared.worker.js', 'zhao')
注意: 多个页面调用构造函数传入同一 shared.worker.js 文件,只有第一个页面会创建 SharedWorker,其他页面会复用已创建的 SharedWorker,这个 SharedWorker 由那几个页面共享。
然后,在 shared.worker.js 文件定义消息监听的逻辑,作如下约定:定义一个变量 shareData 用于存储数据,如果 message 事件的传参是 {get: true},则认为一个读数据的操作,调用 port.postMessage(shareData) 向页面发送消息;否则,认为是一个写数据的操作,将数据存于变量 shareData 。
// shared.worker.js
var shareData = null
self.addEventListener('connect', (e) => {
const port = e.ports[0]
port.addEventListener('message', (event) => {
if (event.data.get === true) {
port.postMessage(shareData)
} else {
shareData = event.data
}
})
port.start()
})
之后,页面中定义 sharedWorker.port 对象的 message 事件的监听函数,接收 Shared Worker 发送的数据:
// 监听 Shared Worker 发送的数据
sharedWorker.port.addEventListener('message', (e) => {
console.log('[Shared Worker]接收:', e.data)
}, false)
sharedWorker.port.start()
// 或者
sharedWorker.port.onmessage = (e) => {
console.log('[Shared Worker]接收:', e.data)
}
注意: 使用 sharedWorker.port.addEventListener 来添加监听函数,需要显式调用 sharedWorker.port.start();如果使用onmessage 绑定监听函数则不需要。
最后,在页面通过调用 sharedWorker.port.postMessage 读取或者写入共享数据。
// 写数据
sharedWorker.port.postMessage({msg: 'Hello, This is Page A!'})
// 读数据,会触发 message 事件
sharedWorker.port.postMessage({get: true})
sharedWorker.port 对象上 postMessage() 方法的调用会触发 Shared Worker 的 message 事件,该事件中的 postMessage() 方法又会触发页面上 sharedWorker.port 对象的 message 事件,从来实现多页面之间的数据共享。
SharedWorker DEMO Page A.html?type=4)
SharedWorker DEMO Page B.html?type=4)
SharedWorker DEMO Page C.html?type=4)
注意: 所有需要使用共享数据的页面,都需要基于同一个 Shared Worker 脚本文件(如,shared.worker.js)创建实例,并在页面上添加 sharedWorker.port 的监听函数、调用 postMessage 读写数据。
注意: chrome://inspect/#workers -> Shared workers 可以调试 shared.worker.js。
注意 Shared Worker 的兼容性: 兼容性较差,目前(2021.12),IE 6~11、safari、大部分移动端浏览器都不支持。
IndexedDB
除了可以利用 Shared Worker 来存储共享数据,还可以使用其他一些存储方案。如:IndexedDB、LocalStorage、 Cookie 等。
其思路很简单:与 Shared Worker 方案类似,消息发送方将消息存至共享中心(IndexedDB、LocalStorage、 Cookie);接收方(同源下所有页面)则通过类似 get 的方法或者在事件中获取共享中心的最新的信息。
postMessage
上述所讲的五种通信方式,有一个共同的限制:只能在同源的页面间相互通信。所谓指的是具有相同的协议、相同的域名、相同的端口。
window.postMessage() 方法提供了一种受控机制来规避此限制,只要正确的使用,就可以安全地实现跨源通信。
从广义上讲,一个窗口可以获得对另一个窗口的 window 对象(如:targetWindow = window.opener),然后在窗口上调用 targetWindow.postMessage() 方法发送消息,该消息可以被目标窗口的全局事件 message 接收。
targetWindow.postMessage(message, targetOrigin, [transfer])
- targetWindow: 其他窗口的一个引用。通过以下方法获取:
- 调用
window.open()返回的窗口对象; - 页面上的 iframe 对象:比如 iframe 的
contentWindow属性、、或者是命名过或数值索引的window.frames。
- 调用
- message:将要发送到其他 window 的数据。
- targetOrigin: 指定哪些窗口能接收该消息,其值可以是字符串
*,表示无限制,也可以是一个 URI。如果目标窗口的协议、域名、端口这三者的任意一项不匹配targetOrigin提供的值,那么消息就不会被发送。 - transfer 可选,是一串和
message同时传递的Transferable对象。
以 window.open 为例:
// 父页面:23-commumication(A).html
var targetWindow = window.open('23-commumication(B).html?type=5')
window.addEventListener('message', (e) => {
console.log(`[postMessage]接收:${e.data}`)
})
// 子页面:23-commumication(B).html
window.addEventListener('message', (e) => {
console.log(`[postMessage]接收:${e.data}`)
})
父页面向子页面发送消息:
targetWindow.postMessage('Hello, This is Page A!')
子页面向父页面发送消息:
window.opener.postMessage('Hello, This is Page B!')
注意: 如果不是当前页面调用 window.open() 打开或者 iframe 内嵌的页面,则跟当前页面没有任何关系,即取不到页面窗口的 window 对象,也就无法使用 postMessage 方法发送信息。
注意: 如果 window.open() 打开或者 iframe 内嵌的页面是同源页面,则 postMessage 方法中的第二个参数是不传;如果是非同源页面,则第二个参数必传(可以传 * 或 URI),否则会报错,如:
Failed to execute 'postMessage' on 'DOMWindow': The target origin provided ('http://localhost:5200') does not match the recipient window's origin ('http://localhost:5300').
postMessage+window.open
实现思路: 在页面 A (父窗口)调用 window.open() 打开多个子页面 B、C...(即多个子窗口),并将子页面的 window 对象存在一个数组中。如果页面 A 需要广播消息,只需遍历数组,调用数组中 window 对象的 postMessage 方法发送消息即可;如果子页面 B 需要广播消息,则调用 window.opener.postMessage 方法向页面 A 发送消息,页面 A 再在 message 事件中遍历数组,调用数组中 window 对象的 postMessage 方法发送消息。
首先,在页面 A 把 window.open() 方法打开的页面的 window 对象储存到数组,然后定义 message 事件的监听函数:
var targetWindows = []
function fnOpenWindow(url) {
let tWindow = window.open(url)
targetWindows.push(tWindow)
}
window.addEventListener('message', (e) => {
if (e.data.from === location.href) {
return
}
targetWindows = targetWindows.filter(w => !w.closed)
targetWindows.forEach(w => w.postMessage(e.data, '*'))
console.log('[window.open]接收信息:', `msg-${e.data.msg},from-${e.data.from}`)
document.querySelector('.box_05').querySelector('.content').innerHTML = e.data.msg
})
然后,在各子页面定义 message 事件的监听函数:
window.addEventListener('message', (e) => {
if (e.data.from === location.href) {
return
}
console.log('[window.open]接收信息:', `msg-${e.data.msg},from-${e.data.from}`)
})
最后,在子页面(如 B)中调用 postMessage 方法,实现广播消息:
window.opener.postMessage({
msg: "Hello, This is Page B!",
from: location.href
}, '*')
window.open DEMO Page A.html?type=5)
注意: 子页面的 window 对象上的 opener 属性,指向的是父页面的 window 对象,因此,子页面获得了父页面的控制权。出于安全考虑,可以限制子页面的 window.opener 属性。如果是 a 标签跳转,可以加 rel=noopener 属性或者 rel=noreferrer,如果是 Js 调用 window.open() 方法,可以:
let childWindow = window.open(url)
childWindow.opener = null;
注意: 该方案使用有限制,被打开页面必需要有 window.opener 属性,并且指向打开该页面的页面 window 对象。即,如果子页面不是通过在另一个页面内的 window.open 打开的(如直接在地址栏输入链接或者从其他网站链接过来),则两者之间没有联系,无法通信。
postMessage+iframe
该方案与 postMessage+window.open 类似,区别在于:适用于在当前页面内嵌 iframe 子页面的场景。父页面调用类似 window.frames[0].postMessage() 方法向子页面发送信息,子页面在 message 事件中调用 window.parent.postMessage() 或者 e.source.postMessage() 向父页面通信。
实现思路: 非同源页面 A 和 B ,A 内嵌一个 iframe 页面 C1,B 内嵌一个 iframe 页面 C2,C1、C2 指向同一 URL(或同源下的不同 URL 也可以)。当页面 A 需要向 页面 B 通信时,先将消息发给其内嵌的 iframe C1,由于 C1、C2 同源,它们之间可以使用上述讲到的任意方法通信,即 C2 可以接收 C1 发送的消息,然后再由 C2 将发送信息给页面 B。
我们以 BroadCast Channel 为例。
首先,在中间页面创建一个 BroadCast Channel 实例,并为实例的 message 事件上定义函数,接收来自 BroadCast Channel 的消息,再定义一个全局的 message 事件定义函数,接收来自父页面的消息。
const BC = new BroadcastChannel('zhao')
// 接收到 BroadCast Channel 广播消息,发送给父页面
BC.onmessage = function (e) {
window.parent.postMessage(e.data, '*')
console.log('[BroadCast Channel]接收:', `msg-${e.data.msg},from-${e.data.from}`)
}
// 接收来自父页面的消息,调用 BroadCast Channel 广播消息
window.addEventListener('message', function (e) {
BC.postMessage(e.data)
console.log('[父页面]接收:', `msg-${e.data.msg},from-${e.data.from}`)
})
然后,在页面 A、B 中定义全局的 message 事件函数,接收来自 iframe 的消息:
window.addEventListener('message', (e) => {
console.log('[iframe]接收:', `msg-${e.data.msg},from-${e.data.from}`)
})
最后,在页面 A 或者 B 发出消息:
window.frames[0].postMessage({
msg: "Hello, This is Page A!",
from: location.href
}, '*')
iframe DEMO Page A.html?type=6)
iframe DEMO Page B.html?type=6)
注意: 主页面调用 iframe 的 postMessage 方法,需要等 iframe 加载完。否则报错:
Failed to execute 'postMessage' on 'DOMWindow': The target origin provided ('http://xxx.xxx.x.xxx:xxxx') does not match the recipient window's origin
window.name+iframe
浏览器的 window.name 属性有个特性:无论是否同源,只要在同一个浏览器标签或者同一个 iframe 框架打开过的页面,后一个页面可以读取前一个页面设置的 window.name 值(页面刷新后,该值还是存在)。
注意: window.name 的值大小可达 2MB。
根据这个特性,我们可以在主页面内嵌 iframe,先将 iframe 指向一个非同源的页面,然后将 iframe 指向一个与主页面同源的中间页面,再主页面访问同源是中间页面的 window.name 值,间接实现非同源页面的通信。
比如:http://localhost:5200/23-commumication(A).html 要获取非同源页面 http://localhost:5300/23-commumication(B).html 的数据:
首先,在 23-commumication(B).html 页面,将数据赋值给 window.name:
<!-- http://localhost:5300/23-commumication(B).html?type=7 -->
<script>
window.name = "Hello, This is Page B!"
</script>
其次,在 23-commumication(A).html 内嵌一个指向 23-commumication(B).html 的 iframe 标签,在 23-commumication(B).html文件加载完成后,会执行文件中的 window.name 赋值语句。
<!-- http://localhost:5200/23-commumication(A).html?type=7 -->
<iframe src="http://localhost:5300/23-commumication(B).html?type=7"></iframe>
<script>
function getCrossOriginData() {
const frame = document.querySelector("#windowOpenIframe")
frame.onload = function () {
// console.log(frame.contentWindow.name)
}
}
getCrossOriginData()
</script>
注意: 由于 23-commumication(A).html 和 23-commumication(B).html 是非同源页面,无法通过 frame.contentWindow.name 直接获取数据。如果执行上面注释的 console.log(frame.contentWindow.name),会报错:
Uncaught DOMException: Blocked a frame with origin "http://localhost:5200" from accessing a cross-origin frame.
然后,创建一个中间页面 http://localhost:5200/proxy.html(也可以是不存在的页面,会报 404 错误,但不影响功能),然后将 23-commumication(A).html 中 iframe 标签的 src 指向这个中间页面。
由于 proxy.html 和 23-commumication(B).html 是在同一个 iframe 中打开的,它们共享 window.name。而 proxy.html 和 23-commumication(A).html 是同源的,23-commumication(A).html 可以通过 frame.contentWindow.name 获取 proxy.html 的 window.name 值,也就是 23-commumication(B).html 的 window.name 值,如此,间接实现了的 23-commumication(A).html 和 23-commumication(B).html 的非同源通信。
修改 getCrossOriginData 方法如下:
function getCrossOriginData() {
const frame = document.querySelector("#windowOpenIframe")
let isFirstLoad = true
frame.onload = function () {
// iframe 加载完 23-commumication(B).html 后,再去加载 proxy.html,两者共享 window.name 值。
if (isFirstLoad === true) {
isFirstLoad = false
frame.src = 'http://localhost:5200/proxy.html'
} else {
// 获取 proxy.html、23-commumication(B).html 共享的 window.name 值
console.log(frame.contentWindow.name)
document.querySelector('.box_07').querySelector('.content').innerHTML = frame.contentWindow.name
}
}
}
getCrossOriginData()
总结: 该方案实现的关键在于 proxy.html文件,它和 23-commumication(B).html 是在同一个 iframe 访问的,并且它和 23-commumication(A).html 是同源的。
同理,该方法也可以实现服务端跨源数据请求:服务端需要提供一个页面地址,并将需要返回的数据赋值给 window.name 属性。客户端调用如下方法获取数据:
function getCrossOriginData(targetUrl, proxyUrl, callback) {
const iframe = document.createElement('iframe')
iframe.style.display = 'none'
iframe.src = targetUrl
let isFirstLoad = true
iframe.onload = function () {
if (isFirstLoad) {
isFirstLoad = false
iframe.contentWindow.location = proxyUrl
} else {
callback(iframe)
iframe.contentWindow.document.write('')
iframe.contentWindow.close()
document.body.removeChild(iframe)
}
}
document.body.appendChild(iframe)
}
getCrossOriginData(
'http://localhost:5300/23-commumication(B).html?type=7',
'http://localhost:5200/proxy.html.html',
(iframe) => {
console.log(iframe.contentWindow.name)
document.querySelector('.box_07').querySelector('.content').innerHTML = iframe.contentWindow.name
}
)
查看 window.name DEMO.html?type=7)
location.hash+iframe
hash 指的是 URL 的 # 号后面的部分。URL 的 hash 变化,浏览器不会刷新页面。
在父窗口中,通过 iframe 内嵌子窗口。父窗口将数据写入子窗口 URL 的 hash 中,子窗口可通过监听 hashchange事件获取:
// 父窗口
function sendLocationHashToFrame () {
const frame = document.querySelector('#locationHashIframe')
frame.src = frame.src + `#Hello, This is Page A`
}
window.onhashchange = () => {
console.log(window.location.hash)
}
同理,子窗口也可以向父窗口的 hash 写入数据,被父窗口的 hashchange 事件获取:
// 子窗口
function sendLocationHashToParent (e) {
window.parent.location.href = 'http://localhost:5200/23-commumication(A).html?type=8#Hello, This is Page B!'
}
window.onhashchange = () => {
console.log(window.location.hash)
}
查看 location.hash DEMO.html?type=8)
注意: 只有当 hash 值有变化才能触发 message 事件,即如果设置的 hash 值与当前链接相同,不会触发 message 事件。
注意: window.parent.location.href 是唯一的可以跨源访问的 Location 对象属性,且该属性只能跨源赋值,不能跨源取值。
其他方法
URL?param=xxx
即,在页面跳转或打开一个新链接时,在 URL 地址后面加参数。
比如:跳转到 http://localhost:5200/23-commumication(A).html。
location.href = 'http://localhost:5200/23-commumication(A).html?param=value'
这种方式的局限在于,只能从当前页面向被打开的页面传消息,被打开的页面无法反向传消息。
WebSocket
WebSockets 是一种先进的技术,它可以在用户的浏览器和服务器之间打开交互式通信会话。
实现思路: 将 WebSockets 当作消息中转中心,浏览器页面将要发送的消息,发到 WebSockets,再由 WebSockets 发送到各页面。实现原理与 Service workers 类似。
方法分析
以上方法的特点:
BroadCast Channel: 广播监听模式,适用于多个窗口打开的同源页面之间的通信。兼容性较差,Safari@15.2(包括IOS版本)及以下的不支持。
Service workers: 广播监听模式,与 BroadCast Channel 类似,但兼容性比较好,除 IE,其他浏览器的新版本基本都支持。
LocalStorage: 广播监听模式,与 BroadCast Channel 类似,但兼容性好,主流浏览器都支持。
Shared Worker: 数据共享模式,适用于同源页面之间通信,需要主动去获取数据。兼容性差,IE、safari、大部分移动端浏览器都不支持。
IndexedDB: 数据共享模式,与 Shared Worker 类似。但兼容性较好,除 IE,其他主流浏览器都支持。
postMessage: 广播监听模式,适用于有关联的(window.open 打开的、frame 内嵌的)多个窗口的同源和非同源页面之间通信,兼容性好,主流浏览器都支持。
postMessage+window.open: 适用于
window.open打开的多个窗口之间通信。postMessage+iframe。适用于 frame 内嵌的多个窗口之间通信。
window.name+iframe: 数据共享模式,适用于同一个窗口或 iframe 打开的同源和非同源页面之间通信。
location.hash+iframe: 传递监听模式,适用于当前页面与 iframe 之间的同源和非同源页面通信。
其他: URL?param=xxx,传递模式,适用于后一个页面获取前一个同源和非同源页面传递的信息;WebSocket,广播监听模式,适用于同源和非同源页面之间通信。此外,还有以服务端或者 LocalStorage 等为存储中心的数据共享模式等。
数据共享模式主动获取数据的方式有两种:
- 轮询: 使用
setInterval、setTimeout等方法轮询。 - 事件触发: 比如页面生命周期,通过
onvisibilitychange、onpageshow等事件回调:
// 选项卡的内容变得可见或被隐藏时,触发 visibilitychange 事件。
document.addEventListener("visibilitychange", function() {
if (document.visibilityState === 'visible') {
}
})
// 页面切换后显示之后,触发 pageshow 事件
window.addEventListener("pageshow", () => {
...
})
总结:
- 同一窗口同源页面通信,推荐使用 LocalStorage 数据共享模式;
- 同一窗口非同源页面通信,推荐使用 window.name 数据共享模式;
- 不同窗口同源页面通信,推荐使用 localStorage 广播监听模式;
- 不同窗口非同源页面,并且子页面是同一主面调用 window.open 打开的,页面之间通信,推荐使用 postMessage+window.open 广播监听模式;
- 不同窗口非同源页面,并且窗口之间没有关联的页面之间的通信,推荐使用 postMessage+iframe 广播监听模式,或者 window.name+iframe 数据共享模式;
- iframe 父子窗口的通信,推荐使用 location.hash+iframe 传递监听模式。
Web 开发中比较常见的两种页面通信场景:
- 跳新窗口,让用户登录授权:登录完成后,将信息返回上一页面。推荐使用 postMessage+window.open。
- 同一窗口中,A 页面跳转到 B 页面,B 页面回退后,需要更新 A 页面数据: 同源页面推荐使用 LocalStorage,非同源页面推荐使用 window.name、或者以服务端为共享中心的数据共享模式。