瀏覽器多標籤頁通訊
瀏覽器多標籤頁通訊有助於降低伺服器負載,提高運營人員的工作效率,提高使用者體驗。是前端開發優化的一個重要環節。
需求來源
在多數CMS(內容管理系統)後臺上,常見的是一個文章列表頁面,點選列表項會開啟一個新的文章詳情頁面。編輯人員經常在這個詳情頁面上對文章操作,比如修改標題、配圖、摘要等內容。操作完畢之後,由於文章頁和列表頁是兩個頁面,文章內容資料不能及時同步到列表,這樣就照成運營人員多次誤操作,這大大降低了運營人員的工作效率。
對於前端工程師來講,實現瀏覽器多個頁卡之間的通訊,及時更新相關資料更改,是一件重要的事情。
例如有一個需求:當文章詳情頁面更新的時候,會同步到文章列表頁。
實現方式
方式一:cookie+setInterval
cookie最初是在客戶端用於儲存使用者的會話資訊的。由於HTTP是一種無狀態的協議,伺服器單從網路連線上無法知道客戶身份。通過cookie就給客戶端們頒發一個通行證,每人一個,無論誰訪問都必須攜帶自己通行證。這樣伺服器就能從通行證上確認客戶身份了。cookie實際上是一小段的文字資訊。客戶端請求伺服器,如果伺服器需要記錄該使用者狀態,就使用response向客戶端瀏覽器頒發一個cookie。客戶端瀏覽器會把cookie儲存起來。當瀏覽器再請求該網站時,瀏覽器把請求的網址連同該cookie一同提交給伺服器。伺服器檢查該cookie,以此來辨認使用者狀態。伺服器還可以根據需要修改cookie的內容。
在JavaScript中,cookie的操作介面即document.cookie,通過這個介面可以讀取、寫入、刪除cookie。這個操作其實不太友好,所以很多工具庫提供了cookie的操作方法。我這裡提供一個簡單的封裝方法。
var QQ = {}; QQ.Cookie={ set:function(name,value,expires,path,domain){ if(typeof expires=="undefined"){ expires=new Date(new Date().getTime()+3600*1000); } document.cookie=name+"="+escape(value)+((expires)?"; expires="+expires.toGMTString():"")+((path)?"; path="+path:"; path=/")+((domain)?";domain="+domain:""); }, get:function(name){ var arr=document.cookie.match(new RegExp("(^| )"+name+"=([^;]*)(;|$)")); if(arr!=null){ return unescape(arr[2]); } return null; }, clear:function(name,path,domain){ if(this.get(name)){ document.cookie=name+"="+((path)?"; path="+path:"; path=/")+((domain)?"; domain="+domain:"")+";expires=Fri, 02-Jan-1970 00:00:00 GMT"; } } };
cookie有個特性,一個頁面產生的cookie能被與這個頁面的同一目錄或者其他子目錄下的頁面訪問,這樣頁面之間就產生了一個共享的儲存空間。通常把cookie的path設定為一個更高級別的目錄,比如預設“/”,從而使更多的頁面共享cookie,實現多頁面之間相互通訊。 cookie所在的域,預設為請求的地址,也可以通過設定document.domain為父域等方式擴大cookie可被訪問的域。
實現原理:
列表頁通過setInterval定時器迴圈監聽cookie的資料變動
列表頁程式碼:
window.onload=function(){ var tid =''; setInterval(function(){ if(tid != QQ.Cookie.get("tid")){ alert('資料更新!'); tid = QQ.Cookie.get("tid") } }, 1000); }
當詳情頁有資料修改時後,寫入cookie
詳情頁程式碼:
<input id="content" type="text"> <button id="btn">Click</button> <script> window.onload=function(){ var oBtn=document.getElementById("btn"); var oInput=document.getElementById("content"); oBtn.onclick=function(){ var val=oInput.value; QQ.Cookie.set("tid",val); } } </script>
cookie+setInterval的不足:
1、cookie空間有限,瀏覽器在每一個域名下最多能設定30-50個cookie,容量最多4K左右。
2、每次HTTP請求會把當前域的cookie傳送到伺服器上,而有些cookie只是瀏覽器才用的到,浪費網路頻寬。
3、setInterval的頻率設定,過大會影響瀏覽器效能,過小會影響時效性。
cookie+setInterval的優點:
相容性好,幾乎所有的瀏覽器都支援。
方式二:localStorage
在HTML5中,新加入了一個localStorage特性,這個特性主要是用來作為本地儲存來使用的,解決了cookie儲存空間不足的問題,localStorage中一般瀏覽器支援的是5M大小,這個在不同的瀏覽器中localStorage會有所不同。
localStorage的API也很簡單,提供了JS的讀寫操作。
if(!window.localStorage){ alert("瀏覽器不支援localstorage"); return false; }else{ var storage = window.localStorage; //通過屬性寫入a欄位 storage.a = 1; //通過方法寫入b欄位 storage.setItem("b",2); storage.getItem("a"); storage.b; storage.clear(); }
它還比cookie多了一個優點,提供了onstorage以及storage事件,可以繫結一個回撥函式,使用如下:
window.onstorage = function(e){console.log(e)} // 或者 window.addEventListener('storage', function(){ console.log(e)})
localStorage是Storage物件的例項。對Storage物件進行任何修改,都會在觸發storage事件。當通過屬性或者setItem()方法儲存資料,或者使用delete操作符或removeItem()刪除資料,或者呼叫clear()方法時,都會觸發該事件。通過這個事件,我們可以實現頁卡之間的變動監聽。
實現原理:
列表頁通過storage監聽localStorage的資料變動
列表頁程式碼:
<script> window.addEventListener("storage",function(event){ console.log("newValue is"+localStorage.getItem("tid")); console.log("oldValue is"+event.oldValue); window.alert('資料更新!'); },false); </script>
當詳情頁有資料修改時後,寫入localStorage
詳情頁程式碼:
<input id="content" type="text"/> <button id="btn">Click</button> <script> window.onload=function(){ var oBtn=document.getElementById("btn"); var oInput=document.getElementById("content"); oBtn.onclick=function(){ var val=oInput.value; localStorage.setItem("tid",val); } } </script>
不過,onstorage以及storage事件,針對都是非當前頁面對localStorage進行修改時才會觸發,當前頁面修改localStorage不會觸發監聽函式。還有就是在對原有的資料的值進行修改時才會觸發,比如原本已經有一個key為a,值為1的localStorage,再執行:localStorage.setItem('a', 1)程式碼,同樣是不會觸發監聽函式的。
localStorage的不足:
1、瀏覽器的容量大小不統一(比cookie大很多了),並且在高版本的瀏覽器才支援localStorage這個屬性
2、目前所有的瀏覽器中都會把localStorage的值型別限定為string型別,需要JSON轉換。
3、localStorage本質上是對字串的讀取,如果儲存內容多的話會消耗記憶體空間,會導致頁面變卡。
4、localStorage只能監聽非己頁面的資料變化,這一點嚴重影響使用。
localStorage的優點:
1、解決了cookie容量小和時效性不足的問題。
方式三:WebSocket
WebSocket API是下一代客戶端--伺服器的非同步通訊方法,已被W3C進行了標準化。WebSocket API最偉大之處在於伺服器和客戶端可以雙向實時通訊。WebSocket並不限於以Ajax(或XHR)方式通訊,因為Ajax技術需要客戶端發起請求,而WebSocket伺服器和客戶端可以彼此相互推送資訊;XHR受到域的限制,而WebSocket允許跨域通訊。
它的使用很簡單,如下:
// 建立一個Socket例項 var socket = new WebSocket('ws://localhost:8080'); // 開啟Socket socket.onopen = function(event) { // 傳送一個初始化訊息 socket.send('I am the client and I\'m listening!'); // 監聽訊息 socket.onmessage = function(event) { console.log('Client received a message',event); }; // 監聽Socket的關閉 socket.onclose = function(event) { console.log('Client notified socket has closed',event); }; // 關閉Socket.... socket.close() };
WebSocket提供了send方法和onmessage事件,用來發送和接收資料。onmessage事件提供了一個data屬性,它可以包含訊息的Body部分。訊息的Body部分必須是一個字串,可以進行序列化/反序列化操作,以便傳遞更多的資料。
實現原理:
列表頁通過onmessage監聽socket伺服器傳送過來的訊息
列表頁程式碼:
<script> var socket = new WebSocket('ws://localhost:8080'); socket.onopen = function(event) { socket.onmessage = function(event) { console.log('Client received a message', event); }; }; </script>
當詳情頁有資料修改時後,通過socket連線,通知列表頁更新資料。
詳情頁程式碼:
<input id="content" type="text"/> <button id="btn">Click</button> <script> var socket = new WebSocket('ws://localhost:8080'); window.onload=function(){ var oBtn=document.getElementById("btn"); var oInput=document.getElementById("content"); oBtn.onclick=function(){ var val=oInput.value; socket.onopen = function(event) { // 傳送資料型別必須是string、ArrayBuffer、Blob之一 socket.send('資料更新!'); } } </script>
WebSocket的語法非常簡單,不過需要IE10+瀏覽器才支援WebSocket通訊。如果你的業務需要相容IE8,9。業界通常使用第三方庫來解決這個問題,比如Socket.IO,它使用檢測功能來判斷是否建立WebSocket連線,或者是AJAX long-polling連線,或Flash等,可快速建立實時的應用程式。Socket.IO還提供了一個NodeJS API,它看起來非常像瀏覽器的API。
WebSocket的不足:
1、它需要服務端的支援才能完成任務。如果socket資料量比較大的話,會嚴重消耗伺服器的資源。
WebSocket的優點:
1、使用簡單,功能靈活、強大,如果部署了WebSocket伺服器,可以實現很多實時的功能。
方式四:BroadcastChannel
BroadcastChannel即廣播頻道,是window下面的一個API,該API是用於同源不同頁面之間完成通訊的功能。我們可以理解它是一個廣播臺,所有的廣播例項,都會接入這個廣播臺(中介者模式中的控制中心),所以,只要在初始化例項時,傳入相同的頻道值,就會被接入到一個相同的廣播頻道中。它的實現最簡單,很多第三方JS庫都實現了一套自己的BroadcastChannel。
實現原理:
列表頁通過onmessage監聽其他頁面傳送過來的訊息
列表頁程式碼:
// 接收廣播 let articleCast = new BroadcastChannel('mychannel'); articleCast.onmessage = function (e) { console.log(e.data); }
當詳情頁有資料修改時後,通過postMessage,傳遞資料。
詳情頁程式碼:
// 建立廣播併發送 let listCast = new BroadcastChannel('mychannel'); myObj = { tid: "123", title: "更改後的標題" }; listCast.postMessage(myObj);
BroadcastChannel的不足:
1、相容性極差,只支援最新版的Chrome和Firefox,完全不支援IE和Safari。
BroadcastChannel的優點:
1、使用簡單,功能單一,跨頁面通訊的理想選擇。
方式五:SharedWorker
SharedWorker也是HTML5提供的新的瀏覽器API,叫共享工作執行緒。它允許多個頁面共享使用執行緒,每個頁面都連結到該共享工作執行緒的某個埠號上。頁面通過該埠與共享工作執行緒進行通訊。目前的Web所有程式的操作都基於頁面的,而SharedWorker的引入開闢了一個“Web程式”在後臺執行緒的概念。而且它還可以和頁面互動,相當於把所有頁面都聚攏起來了。上例講為每個頁面都維護一份WebSocket程式碼不僅耗費大量的連線數,而且還拖慢效能。這些通用的連線最好當然做成可跨域頁面共用的,在SharedWorker引入之前並沒有一個完美的跨頁面通訊解決方案。
實現原理
列表頁通過onmessage監聽SharedWoker傳送過來的訊息
列表頁程式碼:
<script> var s = new SharedWorker('x.js'); s.port.onmessage = function(e){ console.log(e.data); window.alert("資料變化!") }; s.port.start(); </script>
當詳情頁有資料修改時後,通過SharedWorker,通知列表頁更新資料。
<input id="content" /><input type="button" id="btn" value="傳送" /> <script> var s = new SharedWorker('x.js'); btn.onclick=function(){ s.port.postMessage(document.getElementById('content').value); }; s.port.start(); </script>
其中共享執行緒x.js的程式碼也很簡單,它的工作是雙向的,每一個頁面都可以用來接收和傳送資料。
//x.js var pool = []; onconnect = function(e) { pool.push(e.ports[0]); e.ports[0].onmessage = function(e){ for(var i=0; i<pool.length; i++) pool[i].postMessage(e.data); }; };
SharedWorker就像執行在瀏覽器後端的守衛者,可以被多個window同時使用,但必須保證這些標籤頁都是同源的(相同的協議,主機和埠號)。
SharedWorker的不足:
1、相容性較差,IE完全不支援,chrome和Firefox支援很完善,Safari部分支援,如果你的業務是內部系統,不考慮IE,可以使用。
2、API比較簡單,配置繁瑣,使用起來還是比較麻煩。
SharedWorker的優點:
1、功能強大,不限於瀏覽器通訊,還有共享資料,方法等功能。由於是另啟的一個新執行緒,不影響主執行緒程式碼業務,效能優秀,無需藉助伺服器,是一個完美的跨頁面通訊解決方案。
我們做了什麼?(劃重點)
SharedWorker提供的API很少,使用比較簡單,如果需要完成複雜的頁面通訊,還是有一定難度。基於此,我實現了一款基於SharedWorker的封裝庫,叫作superSharedWorker
它是一款頁面之間通訊的JavaScript框架,它通過shared worker 實現純瀏覽器頁卡之間的通訊。你無需瞭解shared worker,可以快速使用頁面之間的資料傳遞,快捷,強大。它的優點就是通過原生JS實現,無需依賴任何JS庫實現了對sharedWorker的封裝。開箱即用,配置簡單。
兩種使用方式:
1、ES6 import的方式
import superSharedWorker from './src/index.js'; let superSharedWorker = new superSharedWorker('page1', callback); //註冊 superSharedWorker.send('hello world!'); //傳送訊息
2、script標籤外鏈的形式
<script src="./build/super-sharedworker.js"></script> <script type="text/javascript"> //<!-- var superSharedWorker = new SuperShared('page1', onRecvMsg); function onRecvMsg(message) { console.log(message) } superSharedWorker.send('hello, world'); //--> </script>
更多用法舉例:
let superSharedWorker = new superSharedWorker('page1', callback); //註冊 superSharedWorker.add({name:'chunpengliu', sex:1}); superSharedWorker.del('sex');// 刪除緩衝區資料 superSharedWorker.send({'time':2019}, 'page2'); //一次性發送緩衝區資料,只發送給name="page2"的頁面 superSharedWorker.close(); //關閉執行緒,節省資源
它提供了很多強大的功能,可一對一,一對多傳送訊息。像使用git一樣傳遞資料。
小結
通過討論,實現了四種實現瀏覽器標籤頁之間的通訊,分別是使用cookie、使用websocket協議、通過localstorage、以及使用html5瀏覽器的新特性SharedWorker,每種方法各有利弊。如果不考慮相容舊的瀏覽器,superSharedWorker 或許是最好的解決方案,優化使用效率,提升使用者體驗,趕快使用瀏覽器多標籤頁通訊功能吧!