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

如何利用不一樣中介來協助開發開發人員完成跨標籤頁通信。javascript

Published: 2019-2-20html

Code: Githubjava

業務場景

首先還原下業務場景,main page主頁面的某些操做會使得遊覽器打開新的other page標籤頁,以後當main page發生其餘操做時候,other page也要發生變更。git

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

上述業務場景就是,瀏覽器中的多個選項卡或窗口之間進行通訊(在同一個域上,而不是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 爲打開的新窗口對象的引用
複製代碼

若是父子窗口知足「同源策略」,你能夠經過這個引用訪問新窗口的屬性或方法。cookie

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

基於 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…

相關文章
相關標籤/搜索