摘要:目標(biāo)當(dāng)前頁(yè)面需要與當(dāng)前瀏覽器已打開(kāi)的的某個(gè)頁(yè)通信,完成某些交互。這種方式性能極其低下,需要通信兩方不停的監(jiān)聽(tīng)某項(xiàng)的變化,及其浪費(fèi)事件隊(duì)列處理效率。至此,的消息走通了所有鏈路,成功抵達(dá)。
目標(biāo)
當(dāng)前頁(yè)面需要與當(dāng)前瀏覽器已打開(kāi)的的某個(gè)tab頁(yè)通信,完成某些交互。其中,與當(dāng)前頁(yè)面待通信的tab頁(yè)可以是與當(dāng)前頁(yè)面同域(相同的協(xié)議、域名和端口),也可以是跨域的。
要實(shí)現(xiàn)這個(gè)特殊的功能,單單使用HTML5的相關(guān)特性是無(wú)法完成的,需要有更加巧妙的設(shè)計(jì)。
暢想現(xiàn)在我們發(fā)現(xiàn)下思維,假設(shè)多種場(chǎng)景下的解決方案,最終尋找通用解。
case 1兩個(gè)需要交互的tab頁(yè)面具有依賴關(guān)系。
如 A頁(yè)面中通過(guò)JavaScript的window.open打開(kāi)B頁(yè)面,或者B頁(yè)面通過(guò)iframe嵌入至A頁(yè)面,此種情形最簡(jiǎn)單,可以通過(guò)HTML5的 window.postMessage API完成通信,由于postMessage函數(shù)是綁定在 window 全局對(duì)象下,因此通信的頁(yè)面中必須有一個(gè)頁(yè)面(如A頁(yè)面)可以獲取另一個(gè)頁(yè)面(如B頁(yè)面)的window對(duì)象,這樣才可以完成單向通信;B頁(yè)面無(wú)需獲取A頁(yè)面的window對(duì)象,如果需要B頁(yè)面對(duì)A頁(yè)面的通信,只需要在B頁(yè)面?zhèn)陕?tīng)message事件,獲取事件中傳遞的source對(duì)象,該對(duì)象即為A頁(yè)面window對(duì)象的引用:
B頁(yè)面 window.addEventListner("message",(e)=>{ let {data,source,origin} = e; source.postMessage("message echo","/"); });
postMessage的第一個(gè)參數(shù)為消息實(shí)體,它是一個(gè)結(jié)構(gòu)化對(duì)象,即可以通過(guò)“JSON.stringify和JSON.parse”函數(shù)還原的對(duì)象;第二個(gè)參數(shù)為消息發(fā)送范圍選擇器,設(shè)置為“/”意味著只發(fā)送消息給同源的頁(yè)面,設(shè)置為“*”則發(fā)送全部頁(yè)面。
case 2兩個(gè)打開(kāi)的頁(yè)面屬于同源范疇。
若要實(shí)現(xiàn)兩個(gè)互不相關(guān)的通源tab頁(yè)面通信,可以使用一種比較巧妙的方式:localstorage。localStorage的存儲(chǔ)遵循同源策略,因此同源的兩個(gè)tab頁(yè)面可以通過(guò)這種共享localStorage的方式實(shí)現(xiàn)通信,通過(guò)約定localStorage的某一個(gè)itemName,基于該key值的內(nèi)容作為“共享硬盤”方式通信。
不過(guò),如果單純使用localStorage存儲(chǔ)做通信方式會(huì)遇到一個(gè)問(wèn)題,就是兩個(gè)頁(yè)面把握不準(zhǔn)通信時(shí)機(jī),如果A頁(yè)面此刻需要發(fā)送給B頁(yè)面一條消息“hello B”,它會(huì)設(shè)置localStorage.setItem("message","hello B"),并且采用setTimeout輪訓(xùn)等待B的消息;而B(niǎo)此刻也同樣使用setTimeout輪訓(xùn)等待localStorage的message項(xiàng)的變化,當(dāng)獲取到"message"字段時(shí),便取出消息"hello B"。B如果要發(fā)消息給A,仍然采用同樣套路。
這種方式性能極其低下,需要通信兩方不停的監(jiān)聽(tīng)localStorage某項(xiàng)的變化,及其浪費(fèi)事件隊(duì)列處理效率。幸好,HTML5提供了storage事件,通過(guò)window對(duì)象偵聽(tīng)storage事件,會(huì)偵聽(tīng)localStorage對(duì)象的變化事件(包括item的添加、修改和刪除)。因此,通過(guò)事件可以完成高效的通信機(jī)制:
A 頁(yè)面 window.addEventListener("storage", function(ev){ if (ev.key == "message") { // removeItem同樣觸發(fā)storage事件,此時(shí)ev.newValue為空 if(!ev.newValue) return; var message = JSON.parse(ev.newValue); console.log(message); } }); function sendMessage(message){ localStorage.setItem("message",JSON.stringify(message)); localStorage.removeItem("message"); } // 發(fā)送消息給B頁(yè)面 sendMessage("this is message from A");
B 頁(yè)面 window.addEventListener("storage", function(ev){ if (ev.key == "message") { // removeItem同樣觸發(fā)storage事件,此時(shí)ev.newValue為空 if(!ev.newValue) return; var message = JSON.parse(ev.newValue); // 發(fā)送消息給A頁(yè)面 sendMessage("message echo from B"); } }); function sendMessage(message){ localStorage.setItem("message",JSON.stringify(message)); localStorage.removeItem("message"); }
發(fā)送消息采用sendMessage函數(shù),該函數(shù)序列化消息,設(shè)置為localStorage的message字段值后,刪除該message字段。這樣做的目的是不污染localStorage空間,但是會(huì)造成一個(gè)無(wú)傷大雅的反作用,即觸發(fā)兩次storage事件,因此我們?cè)趕torage事件處理函數(shù)中做了if(!ev.newValue) return;判斷。
當(dāng)我們?cè)贏頁(yè)面中執(zhí)行sendMessage函數(shù),其他同源頁(yè)面會(huì)觸發(fā)storage事件,而A頁(yè)面卻不會(huì)觸發(fā)storage事件;而且連續(xù)發(fā)送兩次相同的消息也只會(huì)觸發(fā)一次storage事件,如果需要解決這種情況,可以在消息體體內(nèi)加入時(shí)間戳:
sendMessage({ data: "hello world", timestamp: Date.now() }); sendMessage({ data: "hello world", timestamp: Date.now() });
通過(guò)這種方式,可以實(shí)現(xiàn)同源下的兩個(gè)tab頁(yè)通信,兼容性
case 3通過(guò)caniuse網(wǎng)站查詢storage事件發(fā)現(xiàn),IE的瀏覽器支持非常的不友好,caniuse使用了“completely wrong”的形容詞來(lái)表述這一程度。IE10的storage事件會(huì)在頁(yè)面document文檔對(duì)象構(gòu)建完成后觸發(fā),這在嵌套iframe的頁(yè)面中造成諸多問(wèn)題;IE11的storage Event對(duì)象卻不區(qū)分oldValue和newValue值,它們始終存儲(chǔ)更新后的值
兩個(gè)互不相關(guān)的tab頁(yè)面通信。
這種情況才是最急需解決的問(wèn)題,如何實(shí)現(xiàn)兩個(gè)沒(méi)有任何關(guān)系的tab頁(yè)面通信,這需要一些技巧,而且需要有同時(shí)修改這兩個(gè)tab頁(yè)面的權(quán)限,否則根本不可能實(shí)現(xiàn)這兩個(gè)tab頁(yè)的能力。
在上述條件滿足的情況下,我們就可以使用case1 和 case2的技術(shù)完成case 3的需求,這需要我們巧妙的結(jié)合HTML5 postMessage API 和 storage事件實(shí)現(xiàn)這兩個(gè)毫無(wú)關(guān)系的tab頁(yè)面的連通。為此,我想到了iframe,通過(guò)在這兩個(gè)tab頁(yè)嵌入同一個(gè)iframe頁(yè)實(shí)現(xiàn)“橋接”,最終完成通信:
tab A -----> iframe A[bridge.html] | | |/ iframe B[bridge.html] -----> tab B
單方向的通信原理如上圖所示,tab A中嵌入iframe A,tab B中嵌入iframe B,這兩個(gè)iframe引用相同的頁(yè)面“bridge.html”。如果tab A發(fā)消息給tab B,首先tab A通過(guò)postMessage消息發(fā)送給iframe A(tab A可以獲取到iframe A的window對(duì)象iframe.contentWindow);此后iframe A通過(guò)storage消息完成與iframe B的通信(由于iframeA 與iframe B同源,因此case 2的通信方式這里可以使用);最終,iframe B同樣采用postMessage方式發(fā)送消息給tab B(在iframe中通過(guò)window.parent引用tab B的window對(duì)象)。至此,tab A的消息走通了所有鏈路,成功抵達(dá)tab B。
反方向發(fā)送消息同樣的道理,這里就不在詳細(xì)說(shuō)明。接下來(lái)到了 talk is cheap,show me the code 環(huán)節(jié):
tab A: // 向彈出的tab頁(yè)面發(fā)送消息 window.sendMessageToTab = function(data){ // 由于[#J_bridge]iframe頁(yè)面的源文件在vstudio服務(wù)器中,因此postMessage發(fā)向“同源” document.querySelector("#J_bridge").contentWindow.postMessage(JSON.stringify(data),"/"); }; // 接收來(lái)自 [#J_bridge]iframe的tab消息 window.addEventListener("message",function(e){ let {data,source,origin} = e; if(!data) return; try{ let info = JSON.parse(JSON.parse(data)); if(info.type == "BSays"){ console.log("BSay:",info); } }catch(e){ } }); sendMessageToTab({ type: "ASays", data: "hello world, B" })
bridge.html window.addEventListener("storage", function(ev){ if (ev.key == "message") { window.parent.postMessage(ev.newValue,"*"); } }); function message_broadcast(message){ localStorage.setItem("message",JSON.stringify(message)); localStorage.removeItem("message"); } window.addEventListener("message",function(e){ let {data,source,origin} = e; // 接受到父文檔的消息后,廣播給其他的同源頁(yè)面 message_broadcast(data); });
tab B window.addEventListener("message",function(e){ let {data,source,origin} = e; if(!data) return; let info = JSON.parse(JSON.parse(data)); if(info.type == "ASays"){ document.querySelector("#J_bridge").contentWindow.postMessage(JSON.stringify({ type: "BSays", data: "hello world echo from B" }),"*"); } }); // tab B主動(dòng)發(fā)送消息給tab A document.querySelector("button").addEventListener("click",function(){ document.querySelector("#J_bridge").contentWindow.postMessage(JSON.stringify({ type: "BSays", data: "I am B" }),"*"); })
至此,通過(guò)在tab A和tab B中引入“橋接”功能的iframe[bridge.html]頁(yè)面,實(shí)現(xiàn)了兩個(gè)無(wú)關(guān)tab頁(yè)的雙向通信,這種實(shí)現(xiàn)的技巧性較強(qiáng)。
參考資料Communication between tabs or windows
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/91875.html
摘要:目標(biāo)當(dāng)前頁(yè)面需要與當(dāng)前瀏覽器已打開(kāi)的的某個(gè)頁(yè)通信,完成某些交互。這種方式性能極其低下,需要通信兩方不停的監(jiān)聽(tīng)某項(xiàng)的變化,及其浪費(fèi)事件隊(duì)列處理效率。至此,的消息走通了所有鏈路,成功抵達(dá)。 目標(biāo) 當(dāng)前頁(yè)面需要與當(dāng)前瀏覽器已打開(kāi)的的某個(gè)tab頁(yè)通信,完成某些交互。其中,與當(dāng)前頁(yè)面待通信的tab頁(yè)可以是與當(dāng)前頁(yè)面同域(相同的協(xié)議、域名和端口),也可以是跨域的。 要實(shí)現(xiàn)這個(gè)特殊的功能,單單使用H...
摘要:關(guān)于我的博客掘金專欄路易斯專欄原文鏈接擴(kuò)展開(kāi)發(fā)定制請(qǐng)求響應(yīng)頭域本文共字,閱讀需分鐘。那么,我會(huì)放棄嗎反向代理顯然不會(huì),既然問(wèn)題出在上,我去掉就行了。然而無(wú)論多少次的學(xué)習(xí)和模仿,最終的目的還是為了使用,故開(kāi)發(fā)一款定制請(qǐng)求的勢(shì)在必行。 本文首發(fā)于《程序員》雜志2017年第9、10、11期,下面的版本又經(jīng)過(guò)進(jìn)一步的修訂。 關(guān)于 Github:IHeader 我的博客:louis blog ...
摘要:前端日?qǐng)?bào)精選的現(xiàn)狀網(wǎng)頁(yè)性能提升指南精讀現(xiàn)代概覽從入門到真實(shí)項(xiàng)目配置瀏覽器的渲染過(guò)程與原理動(dòng)畫(huà)動(dòng)畫(huà)中文第期跨瀏覽器頁(yè)的通信解決方案嘗試前端面試題及答案總結(jié)掘金周刊教程最熟悉的陌生人個(gè)人文章技術(shù)周刊期知乎專欄的缺口和中的樣式從外 2017-09-18 前端日?qǐng)?bào) 精選 Web 的現(xiàn)狀:網(wǎng)頁(yè)性能提升指南精讀《現(xiàn)代 JavaScript 概覽》webpack:從入門到真實(shí)項(xiàng)目配置瀏覽器的渲染:過(guò)...
摘要:一同源頁(yè)面間的跨頁(yè)面通信以下各種方式的在線可以戳這里瀏覽器的同源策略在下述的一些跨頁(yè)面通信方法中依然存在限制。因此,我們先來(lái)看看,在滿足同源策略的情況下,都有哪些技術(shù)可以用來(lái)實(shí)現(xiàn)跨頁(yè)面通信。 引言 在瀏覽器中,我們可以同時(shí)打開(kāi)多個(gè)Tab頁(yè),每個(gè)Tab頁(yè)可以粗略理解為一個(gè)獨(dú)立的運(yùn)行環(huán)境,即使是全局對(duì)象也不會(huì)在多個(gè)Tab間共享。然而有些時(shí)候,我們希望能在這些獨(dú)立的Tab頁(yè)面之間同步頁(yè)面的數(shù)...
摘要:這是本人寫(xiě)的第一個(gè)擴(kuò)展,這個(gè)擴(kuò)展的普遍適用性不強(qiáng),但是確實(shí)很方便,具體的開(kāi)發(fā)流程寫(xiě)在這里,感興趣的看官可以試著自己動(dòng)手寫(xiě)一寫(xiě)這個(gè)擴(kuò)展的作用是,它的適用場(chǎng)景是更換百度搜索頁(yè)的關(guān)鍵詞,并且跳轉(zhuǎn),如果你理解了這個(gè)意思,那你一定會(huì)想這能有什么用,是 這是本人寫(xiě)的第一個(gè)chrome擴(kuò)展,這個(gè)擴(kuò)展的普遍適用性不強(qiáng),但是確實(shí)很方便,具體的開(kāi)發(fā)流程寫(xiě)在這里,感興趣的看官可以試著自己動(dòng)手寫(xiě)一寫(xiě) 這個(gè)擴(kuò)展...
閱讀 630·2023-04-26 01:53
閱讀 2754·2021-11-17 17:00
閱讀 2891·2021-09-04 16:40
閱讀 1992·2021-09-02 15:41
閱讀 839·2019-08-26 11:34
閱讀 1228·2019-08-26 10:16
閱讀 1340·2019-08-23 17:51
閱讀 825·2019-08-23 16:50