跨頁面通信的各種姿勢
作者介紹:nekron 螞蟻金服數據前端
將跨頁面通訊類比計算機進程間的通訊,其實方法無外乎那麼幾種,而web領域可以實現的技術方案主要是類似於以下兩種原理:
* 獲取句柄,定向通訊
* 共享內存,結合輪詢或者事件通知來完成業務邏輯
由於第二種原理更利於解耦業務邏輯,具體的實現方案比較多樣。以下是具體的實現方案,簡單介紹下,權當科普:
一、獲取句柄
具體方案
// parent.htmlnconst childPage = window.open(child.html, child)nnchildPage.onload = () => {n childPage.postMessage(hello, location.origin)n}nn// child.htmlnwindow.onmessage = evt => {n// evt.datan
父頁面通過`window.open(url, name)`方式打開的子頁面可以獲取句柄,然後通過postMessage完成通訊需求。
tips
1. 當指定`window.open`的第二個name參數時,再次調用`window.open(****, child)`會使之前已經打開的同name子頁面刷新
2. 由於安全策略,非同步請求之後再調用`window.open`會被瀏覽器阻止,不過可以通過句柄設置子頁面的url即可實現類似效果
// 首先先開一個空白頁nconst tab = window.open(about:blank)nn// 請求完成之後設置空白頁的urlnfetch(/* ajax */).then(() => {n tab.location.href = ****n})n
優劣
缺點是只能與自己打開的頁面完成通訊,應用面相對較窄;但優點是在跨域場景中依然可以使用該方案。
二、localStorage
具體方案
設置共享區域的storage,storage會觸發storage事件
// A.htmlnlocalStorage.setItem(message, hello)nn// B.htmlnwindow.onstorage = evt => {n// evt.key, evt.oldValue, evt.newValuen}n
tips
1. 觸發寫入操作的頁面下的**storage listener**不會被觸發
2. storage事件只有在發生改變的時候才會觸發,即重複設置相同值不會觸發listener
3. safari隱身模式下無法設置localStorage值
優劣
API簡單直觀,兼容性好,除了跨域場景下需要配合其他方案,無其他缺點
三、BroadcastChannel
具體方案
和`localStorage`方案基本一致,額外需要初始化
// A.htmlnconst channel = new BroadcastChannel(tabs)nchannel.onmessage = evt => {n// evt.datan}nn// B.htmlnconst channel = new BroadcastChannel(tabs)nchannel.postMessage(hello)n
優劣
和`localStorage`方案沒特別區別,都是同域、API簡單,`BroadcastChannel`方案兼容性差些(chrome > 58),但比`localStorage`方案生命周期短(不會持久化),相對乾淨些。
四、SharedWorker
具體方案
`SharedWorker`本身並不是為了解決通訊需求的,它的設計初衷應該是類似總控,將一些通用邏輯放在SharedWorker中處理。不過因為也能實現通訊,所以一併寫下:
// A.htmlnvar sharedworker = new SharedWorker(worker.js)nsharedworker.port.start()nsharedworker.port.onmessage = evt => {n// evt.datan}nn// B.htmlnvar sharedworker = new SharedWorker(worker.js)nsharedworker.port.start()nsharedworker.port.postMessage(hello)nn// worker.jsnconst ports = []nonconnect = e => {nconst port = e.ports[0]n ports.push(port)n port.onmessage = evt => {n ports.filter(v => v!== port) // 此處為了貼近其他方案的實現,剔除自己n .forEach(p => p.postMessage(evt.data))n }n}n
優劣
相較於其他方案沒有優勢,此外,API複雜而且調試不方便。
五、Cookie
具體方案
一個古老的方案,有點`localStorage`的降級兼容版,我也是整理本文的時候才發現的,思路就是往`document.cookie`寫入值,由於cookie的改變沒有事件通知,所以只能採取輪詢臟檢查來實現業務邏輯。
方案比較醜陋,勢必被淘汰的方案,貼一下原版思路地址,我就不寫demo了。
communication between browser windows (and tabs too) using cookies
優劣
相較於其他方案沒有存在優勢的地方,只能同域使用,而且污染cookie以後還額外增加AJAX的請求頭內容。
六、Server
之前的方案都是前端自行實現,勢必受到瀏覽器限制,比如無法做到跨瀏覽器的消息通訊,比如大部分方案都無法實現跨域通訊(需要增加額外的postMessage邏輯才能實現)。通過藉助服務端,還有很多增強方案,也一併說下。
乞丐版
後端無開發量,前端定期保存,在tab被激活時重新獲取保存的數據,可以通過校驗hash之類的標記位來提升檢查性能。
window.onvisibilitychange = () => {nif (document.visibilityState === visible) {n// AJAXn }n}n
Server-sent Events / Websocket
項目規模小型的時候可以採取這類方案,後端自行維護連接,以及後續的推送行為。
SSE
// 前端nconst es = new EventSource(/notification)nnes.onmessage = evt => {n// evt.datan}nes.addEventListener(close, () => {n es.close()n}, false)n// 後端,express為例nconst clients = []nnapp.get(/notification, (req, res) => {n res.setHeader(Content-Type, text/event-stream)n clients.push(res)n req.on(aborted, () => {n// 清理clientsn })n})napp.get(/update, (req, res) => {n// 廣播客戶端新的數據n clients.forEach(client => {n client.write(data:hellonn)n setTimeout(() => {n client.write(event:closendata:closenn)n }, 500)n })n res.status(200).end()n})n
Websocket
`http://socket.io`、`sockjs`例子比較多,略
消息隊列
項目規模大型時,需要消息隊列集群長時間維護長鏈接,在需要的時候進行廣播。
提供該類服務的雲服務商很多,或者尋找一些開源方案自建。
例如MQTT協議方案(阿里雲就有提供),web客戶端本質上也是websocket,需要集群同時支持ws和mqtt協議,示例如下:
// 前端n// 客戶端使用開源的Pahon// port會和mqtt協議通道不同nconst client = new Paho.MQTT.Client(host, port, clientId)nnclient.onMessageArrived = message => {n// message. payloadStringn}nclient.connect({n onSuccess: () => {n client.subscribe(notification)n }n})n// 抑或,藉助flash(雖然快要被淘汰了)進行mqtt協議連接並訂閱相應的頻道,flash再通過回調拋出消息nn// 後端n// 根據服務商提供的Api介面調用頻道廣播接n
原文地址:跨頁面通信
推薦閱讀: