跨標籤頁通訊以及實戰排坑

Yzz發表於2019-02-20

如何利用不同中介來協助開發開發人員完成跨標籤頁通訊。

Published: 2019-2-20

Code: Github

業務場景

首先還原下業務場景,main page主頁面的某些操作會使得遊覽器開啟新的other page標籤頁,之後當main page發生其他操作時候,other page也要發生變動。

具體場景:QQ音樂頁面與QQ音樂播放器,當QQ音樂頁面新增歌曲時,QQ音樂播放器會自動將所操作的音樂新增至播放列表。

跨標籤頁通訊以及實戰排坑

上述業務場景就是,瀏覽器中的多個選項卡或視窗之間進行通訊(在同一個域上,而不是CORS)的具體應用。

其本質上都是利用一箇中間介質進行資料傳輸,而不同介質則會導致其適用場景的不盡相同,常用的有如下幾個解決方案:

  1. 使用 window 物件;
  2. postMessage API;
  3. 使用 cookies
  4. 使用 localStorage

window 物件

利用Window 介面的 open() 方法,是用指定的名稱將指定的資源載入到瀏覽器上下文(視窗 window ,內嵌框架 iframe 或者標籤 tab ),同時該方法會返回一個開啟的新視窗物件的引用。

// strUrl => 新視窗需要載入的url地址,strWindowName => 新視窗的名稱
// strWindowFeatures => 是一個字串值,這個值列出了將要開啟的視窗的一些特性(視窗功能和工具欄) 
let windowObjectReference = window.open(strUrl, strWindowName, [strWindowFeatures]);
// windowObjectReference 為開啟的新視窗物件的引用
複製程式碼

如果父子視窗滿足“同源策略”,你可以通過這個引用訪問新視窗的屬性或方法。

也就是說,只用當兩個 URL 具有相同的協議,域和埠時,才能利用 windowObjectReference 訪問到頁面的相關屬性。

基於 windowObjectReference ,通常有兩種解決跨標籤頁通訊的方法:

  • window.name 結合 setInterval

    父頁面利用 windowObjectReference 修改 name 屬性,子頁面輪詢

    // 父頁面
    let windowObjectReference = null
    document.getElementById('btn').onclick = function() {
    	if (!windowObjectReference) {
    		windowObjectReference = window.open('./other.html', 'Yang')
        } else {
            windowObjectReference.name = windowObj.name + 'NB'
        }
    }
    // 子頁面
     setInterval(function() {
     	document.title = window.name
     }, 1000)
    複製程式碼

    跨標籤頁通訊以及實戰排坑

    這種方法優點是向下相容性好,缺點是利用 setInterval 開銷較大、且 window.name 只能使用字串;

  • window.location.hash 結合 window.addEventListener("hashchange", function, false)

    父頁面利用 windowObjectReference 動態修改 location.hash,子頁面利用 window.addEventListener("hashchange", function, false) 監聽 URL 的片段識別符號更改。

    // 父頁面
    let windowObjectReference = null
    document.getElementById('btn').onclick = function() {
    	if (!windowObjectReference) {
            windowObjectReference = window.open('./other.html', 'Yang')
            // 有坑
            windowObjectReference.onload = function() {
                windowObjectReference.location.hash = 'Yang'
            }
        } else {
            windowObjectReference.location.hash = 
                windowObjectReference.location.hash + 'NB'
            windowObjectReference.focus()
        }
    }
    // 子頁面
    window.addEventListener("hashchange", function(e) {
        document.title = window.location.hash
    }, false);
    複製程式碼

    跨標籤頁通訊以及實戰排坑

    這種方法開銷較小,且 URL 長度受限制。

坑一:

可以從程式碼中可以看出,首次修改 windowObjectReference.location.hash 時,利用了 onload 事件

windowObjectReference.onload = function() {
	windowObjectReference.location.hash = 'Yang'
}
複製程式碼

這是因為呼叫 window.open() 方法以後,遠端 URL 不會被立即載入,載入過程是非同步的。(實際載入這個URL的時間推遲到當前指令碼塊執行結束之後。視窗的建立和相關資源的載入非同步地進行。);

坑二:

如果父頁面重新整理之後,重新 window.open 時,需要主要一定要設定 strWindowName,防止重複開啟;

坑三:

需求設定子頁面不能重新整理,如果父頁面發生重新整理,windowObjectReferencenull,該如何通訊

解決方案是利用子頁面的 window.opener,該屬效能夠返回開啟當前視窗的那個視窗的引用,也就是父頁面。

具體做法是在

// 子頁面
setInterval(function() {
    window.opener.windowObjectReference = window
}, 200)
複製程式碼

坑四:

需求又說了點選後希望自動跳轉到子頁面,呼叫 windowObjectReference.focus()

坑五:

坑三的賦值需要在 beforeunload 事件中 window.opener.windowObjectReference = null

postMessage API

這個解決方案最大的優點是可以安全地實現跨源通訊,提供了一種受控機制來規避此限制,只要正確的使用,這種方法就很安全。

// otherWindow 其他視窗的一個引用,如上述 windowObjectReference
otherWindow.postMessage(message, targetOrigin, [transfer]);
// message 將要傳送到其他 window的資料
// targetOrigin 通過視窗的origin屬性來指定哪些視窗能接收到訊息事件,其值可以是字串"*"(表示無限制)或者一個URI
複製程式碼

利用上述API,我們可以完成通訊工作

// 父頁面
let windowObjectReference = null
document.getElementById('btn').onclick = function() {
    if (!windowObjectReference) {
        windowObjectReference = window.open('./other.html', 'Yang')
        windowObjectReference.onload = function() {
            // windowObjectReference.location.hash = 'Yang'
            windowObjectReference.postMessage('Yang', windowObjectReference.origin)
        }
    } else {
        // windowObjectReference.location.hash = windowObjectReference.location.hash + 'NB'
        windowObjectReference.postMessage('NB', windowObjectReference.origin)
        windowObjectReference.focus()
    }
}
// 子頁面
window.addEventListener("message", function (e) {
    document.title = e.data
    // event.source.postMessage('string', event.origin)
}, false);
複製程式碼

同時還可以利用 event.source.postMessage('string', event.origin) 完成雙向通訊(同域)。

PS: 需要特別注意一點,應用該方法時,一定要對 event.origin 進行過濾,具體可以參考 MDN

cookies

cookies 是伺服器傳送到使用者瀏覽器並儲存在本地的一小塊資料,它會在瀏覽器下次向同一伺服器再發起請求時被攜帶併傳送到伺服器上。

用它來完成跨標籤頁通訊,其實和 window.name 的應用方法差不多,在實際場景中 cookies 中儲存著 session 、token 等登陸資訊,一般不建議用來通訊,可以用來父頁面和子頁面共享登陸資訊。

localStorage

localStorage 需要結合 window.onstorage 就是,父頁面修改 localStorage,子頁面能夠監聽到它的變化。

// 父頁面
let windowObjectReference = null
document.getElementById('btn').onclick = function() {
    if (!windowObjectReference) {
        windowObjectReference = window.open('./other.html', 'Yang')
        windowObjectReference.onload = function() {
            localStorage.setItem('hero', 'Yang')
        }
    } else {
        localStorage.setItem('hero', 'NB')
        windowObjectReference.focus()
    }
}
// 子頁面
window.onstorage = function (e) {
    document.title = e.newValue
}
複製程式碼

還可以結合 JSON.stringify 傳遞物件等資料結構,由於利用了 window.onstorage 導致該方法的相容性不是很好,同時 localStorage 是相同域名和埠的不同頁面間可以共享。

跨標籤頁通訊以及實戰排坑

總結

這篇文章的起源是在用QQ音樂時候,點選歌曲,會自動地新增到播放器中,然後不由自主地開啟了 devtool。之後專案中遇到了這個場景,就實現了一次。

參考

javascript.info/cross-windo…

相關文章