將跨頁(yè)面通訊類比計(jì)算機(jī)進(jìn)程間的通訊,其實(shí)方法無(wú)外乎那么幾種捡需,而web領(lǐng)域可以實(shí)現(xiàn)的技術(shù)方案主要是類似于以下兩種原理:
- 獲取句柄办桨,定向通訊
- 共享內(nèi)存,結(jié)合輪詢或者事件通知來(lái)完成業(yè)務(wù)邏輯
由于第二種原理更利于解耦業(yè)務(wù)邏輯站辉,具體的實(shí)現(xiàn)方案比較多樣呢撞。以下是具體的實(shí)現(xiàn)方案,簡(jiǎn)單介紹下饰剥,權(quán)當(dāng)科普:
獲取句柄
具體方案
父頁(yè)面通過(guò)window.open(url, name)
方式打開的子頁(yè)面可以獲取句柄殊霞,然后通過(guò)postMessage完成通訊需求。
// parent.html
const childPage = window.open('child.html', 'child')
childPage.onload = () => {
childPage.postMessage('hello', location.origin)
}
// child.html
window.onmessage = evt => {
// evt.data
}
tips
- 當(dāng)指定
window.open
的第二個(gè)name參數(shù)時(shí)汰蓉,再次調(diào)用window.open('****', 'child')
會(huì)使之前已經(jīng)打開的同name子頁(yè)面刷新 - 由于安全策略绷蹲,異步請(qǐng)求之后再調(diào)用
window.open
會(huì)被瀏覽器阻止,不過(guò)可以通過(guò)句柄設(shè)置子頁(yè)面的url即可實(shí)現(xiàn)類似效果
// 首先先開一個(gè)空白頁(yè)
const tab = window.open('about:blank')
// 請(qǐng)求完成之后設(shè)置空白頁(yè)的url
fetch(/* ajax */).then(() => {
tab.location.href = '****'
})
優(yōu)劣
缺點(diǎn)是只能與自己打開的頁(yè)面完成通訊顾孽,應(yīng)用面相對(duì)較窄祝钢;但優(yōu)點(diǎn)是在跨域場(chǎng)景中依然可以使用該方案。
localStorage
具體方案
設(shè)置共享區(qū)域的storage若厚,storage會(huì)觸發(fā)storage事件
// A.html
localStorage.setItem('message', 'hello')
// B.html
window.onstorage = evt => {
// evt.key, evt.oldValue, evt.newValue
}
tips
- 觸發(fā)寫入操作的頁(yè)面下的storage listener不會(huì)被觸發(fā)
- storage事件只有在發(fā)生改變的時(shí)候才會(huì)觸發(fā)拦英,即重復(fù)設(shè)置相同值不會(huì)觸發(fā)listener
- safari隱身模式下無(wú)法設(shè)置localStorage值
優(yōu)劣
API簡(jiǎn)單直觀,兼容性好测秸,除了跨域場(chǎng)景下需要配合其他方案疤估,無(wú)其他缺點(diǎn)
BroadcastChannel
具體方案
和localStorage
方案基本一致,額外需要初始化
// A.html
const channel = new BroadcastChannel('tabs')
channel.onmessage = evt => {
// evt.data
}
// B.html
const channel = new BroadcastChannel('tabs')
channel.postMessage('hello')
優(yōu)劣
和localStorage
方案沒(méi)特別區(qū)別乞封,都是同域做裙、API簡(jiǎn)單,BroadcastChannel
方案兼容性差些(chrome > 58)肃晚,但比localStorage
方案生命周期短(不會(huì)持久化)锚贱,相對(duì)干凈些。
SharedWorker
具體方案
SharedWorker
本身并不是為了解決通訊需求的关串,它的設(shè)計(jì)初衷應(yīng)該是類似總控拧廊,將一些通用邏輯放在SharedWorker中處理监徘。不過(guò)因?yàn)橐材軐?shí)現(xiàn)通訊,所以一并寫下:
// A.html
var sharedworker = new SharedWorker('worker.js')
sharedworker.port.start()
sharedworker.port.onmessage = evt => {
// evt.data
}
// B.html
var sharedworker = new SharedWorker('worker.js')
sharedworker.port.start()
sharedworker.port.postMessage('hello')
// worker.js
const ports = []
onconnect = e => {
const port = e.ports[0]
ports.push(port)
port.onmessage = evt => {
ports.filter(v => v!== port) // 此處為了貼近其他方案的實(shí)現(xiàn)吧碾,剔除自己
.forEach(p => p.postMessage(evt.data))
}
}
優(yōu)劣
相較于其他方案沒(méi)有優(yōu)勢(shì)凰盔,此外,API復(fù)雜而且調(diào)試不方便倦春。
Cookie
具體方案
一個(gè)古老的方案户敬,有點(diǎn)localStorage
的降級(jí)兼容版,我也是整理本文的時(shí)候才發(fā)現(xiàn)的睁本,思路就是往document.cookie
寫入值尿庐,由于cookie的改變沒(méi)有事件通知,所以只能采取輪詢臟檢查來(lái)實(shí)現(xiàn)業(yè)務(wù)邏輯呢堰。
方案比較丑陋抄瑟,勢(shì)必被淘汰的方案,貼一下原版思路地址枉疼,我就不寫demo了皮假。
communication between browser windows (and tabs too) using cookies
優(yōu)劣
相較于其他方案沒(méi)有存在優(yōu)勢(shì)的地方,只能同域使用骂维,而且污染cookie以后還額外增加AJAX的請(qǐng)求頭內(nèi)容惹资。
Server
之前的方案都是前端自行實(shí)現(xiàn),勢(shì)必受到瀏覽器限制席舍,比如無(wú)法做到跨瀏覽器的消息通訊布轿,比如大部分方案都無(wú)法實(shí)現(xiàn)跨域通訊(需要增加額外的postMessage邏輯才能實(shí)現(xiàn))。通過(guò)借助服務(wù)端来颤,還有很多增強(qiáng)方案汰扭,也一并說(shuō)下。
乞丐版
后端無(wú)開發(fā)量福铅,前端定期保存萝毛,在tab被激活時(shí)重新獲取保存的數(shù)據(jù),可以通過(guò)校驗(yàn)hash之類的標(biāo)記位來(lái)提升檢查性能滑黔。
window.onvisibilitychange = () => {
if (document.visibilityState === 'visible') {
// AJAX
}
}
Server-sent Events / Websocket
項(xiàng)目規(guī)模小型的時(shí)候可以采取這類方案笆包,后端自行維護(hù)連接,以及后續(xù)的推送行為略荡。
SSE
// 前端
const es = new EventSource('/notification')
es.onmessage = evt => {
// evt.data
}
es.addEventListener('close', () => {
es.close()
}, false)
// 后端庵佣,express為例
const clients = []
app.get('/notification', (req, res) => {
res.setHeader('Content-Type', 'text/event-stream')
clients.push(res)
req.on('aborted', () => {
// 清理clients
})
})
app.get('/update', (req, res) => {
// 廣播客戶端新的數(shù)據(jù)
clients.forEach(client => {
client.write('data:hello\n\n')
setTimeout(() => {
client.write('event:close\ndata:close\n\n')
}, 500)
})
res.status(200).end()
})
Websocket
socket.io
、sockjs
例子比較多汛兜,略
消息隊(duì)列
項(xiàng)目規(guī)模大型時(shí)巴粪,需要消息隊(duì)列集群長(zhǎng)時(shí)間維護(hù)長(zhǎng)鏈接,在需要的時(shí)候進(jìn)行廣播。
提供該類服務(wù)的云服務(wù)商很多肛根,或者尋找一些開源方案自建辫塌。
例如MQTT協(xié)議方案(阿里云就有提供),web客戶端本質(zhì)上也是websocket派哲,需要集群同時(shí)支持ws和mqtt協(xié)議臼氨,示例如下:
// 前端
// 客戶端使用開源的Paho
// port會(huì)和mqtt協(xié)議通道不同
const client = new Paho.MQTT.Client(host, port, 'clientId')
client.onMessageArrived = message => {
// message. payloadString
}
client.connect({
onSuccess: () => {
client.subscribe('notification')
}
})
// 抑或,借助flash(雖然快要被淘汰了)進(jìn)行mqtt協(xié)議連接并訂閱相應(yīng)的頻道芭届,flash再通過(guò)回調(diào)拋出消息
// 后端
// 根據(jù)服務(wù)商提供的Api接口調(diào)用頻道廣播接口