跨頁面通信的各種姿勢

作者介紹: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

`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

原文地址:跨頁面通信


推薦閱讀:

看過東西老是記不住該怎麼辦?我是否適合學前端?
如何緊跟前端流行趨勢
OpenDoc - 前端簡歷評級標準

TAG:前端开发 | 前端工程师 |