【每日一題】如何實(shí)現(xiàn)跨頁(yè)面通信?

人生苦短,總需要一點(diǎn)儀式感。比如學(xué)前端~
同源
即同源策略(same-origin policy) 可點(diǎn)擊查看原文
通常,對(duì)于兩個(gè)不同頁(yè)面的腳本,只有當(dāng)執(zhí)行它們的頁(yè)面位于具有相同的協(xié)議(通常為https),端口號(hào)(443為https的默認(rèn)值),以及主機(jī) (兩個(gè)頁(yè)面的模數(shù) Document.domain設(shè)置為相同的值) 時(shí),這兩個(gè)腳本才能相互通信。

同源頁(yè)面之間的通信
支持同源頁(yè)面之間通信的方法有:
BroadCastChannel Service Worker LocalStorage Shared Worker IndexedDB window.open() + window.opener
BroadCast Channel
BroadcastChannel “廣播頻道”接口代理了一個(gè)命名頻道,可以讓指定 origin 下的任意 browsing context 來(lái)訂閱它。
它允許同源的不同瀏覽器窗口,Tab頁(yè),frame或者 iframe 下的不同文檔之間相互通信。
通過(guò)觸發(fā)一個(gè) message 事件,消息可以廣播到所有監(jiān)聽(tīng)了該頻道的 BroadcastChannel 對(duì)象。
它與window.postMessage的區(qū)別:
BroadcastChannel只能用于同源的頁(yè)面之間進(jìn)行通信,而window.postMessage卻可以用于任何的頁(yè)面之間,
此特性在 Web Worker 中可用
MDN:https://developer.mozilla.org/zh-CN/docs/Web/API/BroadcastChannel

Service Worker
這是一個(gè)實(shí)驗(yàn)性的功能ServiceWorker API的 ServiceWorker接口 提供一個(gè)對(duì)一個(gè)服務(wù)工作者的引用。多個(gè)瀏覽上下文(例如頁(yè)面,工作者等)可以與相同的服務(wù)工作者相關(guān)聯(lián),每個(gè)都通過(guò)唯一的ServiceWorker對(duì)象。
MDN:https://developer.mozilla.org/zh-CN/docs/Web/API/ServiceWorker

LocalStorage
localStorage 存儲(chǔ)的數(shù)據(jù)可以長(zhǎng)期保留,只讀的localStorage 屬性允許你訪問(wèn)一個(gè)Document 源(origin)的對(duì)象 Storage。
另外,localStorage 中的鍵值對(duì)總是以字符串的形式存儲(chǔ) (意味著數(shù)值類型會(huì)自動(dòng)轉(zhuǎn)化為字符串類型)
應(yīng)注意,他受同源策略的影響。

當(dāng)出現(xiàn)SecurityError錯(cuò)誤,說(shuō)明請(qǐng)求違反了一個(gè)策略聲明,或者源( origin )不是 一個(gè)有效的協(xié)議/域名/端口 (例如如果origin使用 file: 或者 data: 形式將可能發(fā)生)。比如,用戶可以有對(duì)瀏覽器指定的origin存留數(shù)據(jù)禁用/允許的配置。
MDN: https://developer.mozilla.org/zh-CN/docs/Web/API/Window/localStorage

Shared Worker
SharedWorker 接口代表一種特定類型的 worker,可以從幾個(gè)瀏覽上下文中訪問(wèn),例如幾個(gè)窗口、iframe 或其他 worker。它們實(shí)現(xiàn)一個(gè)不同于普通 worker 的接口,具有不同的全局作用域。
注意:如果要使 SharedWorker 連接到多個(gè)不同的頁(yè)面,這些頁(yè)面必須是同源的(相同的協(xié)議、host 以及端口)。
MDN: https://developer.mozilla.org/zh-CN/docs/Web/API/SharedWorker

IndexedDB
IndexedDB 是一個(gè)事務(wù)型數(shù)據(jù)庫(kù)系統(tǒng),類似于基于 SQL 的 RDBMS。但他是一個(gè)是一個(gè)基于 JavaScript 的面向?qū)ο髷?shù)據(jù)庫(kù),用于在客戶端存儲(chǔ)大量的結(jié)構(gòu)化數(shù)據(jù)(也包括文件/二進(jìn)制大型對(duì)象(blobs))。
我們也可以用它儲(chǔ)存離線數(shù)據(jù)。作為一種底層 API,IndexedDB使用索引實(shí)現(xiàn)對(duì)數(shù)據(jù)的高性能搜索。

注意:正如大多數(shù)的 web 儲(chǔ)存解決方案一樣,IndexedDB 也遵守同源策略。因此當(dāng)你在某個(gè)域名下操作儲(chǔ)存數(shù)據(jù)的時(shí)候,你不能操作其他域名下的數(shù)據(jù)。
此特性在 Web Worker 中可用
MDN:https://developer.mozilla.org/zh-CN/docs/Web/API/IndexedDB_API

window.open() + window.opener
window.open()
Window 接口的 open() 方法,創(chuàng)建一個(gè)新的瀏覽器窗口對(duì)象,如同使用文件菜單中的新窗口命令一樣。
用指定的名稱將指定的資源加載到瀏覽器上下文(窗口 window ,內(nèi)嵌框架 iframe 或者標(biāo)簽 tab )。
如果沒(méi)有指定名稱,則一個(gè)新的窗口會(huì)被打開(kāi)并且指定的資源會(huì)被加載進(jìn)這個(gè)窗口的瀏覽器上下文中。
注意:調(diào)用window.open()方法以后,遠(yuǎn)程 URL 不會(huì)被立即載入,載入過(guò)程是異步的。
MDN:https://developer.mozilla.org/zh-CN/docs/Web/API/Window/open
window.opener
返回打開(kāi)當(dāng)前窗口的那個(gè)窗口的引用
例如:在window A中打開(kāi)了window B,B.opener 返回 A.
如果當(dāng)前窗口是由另一個(gè)窗口打開(kāi)的, window.opener保留了那個(gè)窗口的引用. 如果當(dāng)前窗口不是由其他窗口打開(kāi)的, 則該屬性返回 null.
MDN: https://developer.mozilla.org/zh-CN/docs/Web/API/Window/opener
DOM Level 0 不屬于任何標(biāo)準(zhǔn),不用考慮兼容性直接用!

非同源頁(yè)面之間的通信
可以跨不同源頁(yè)面進(jìn)行通信的方法有:
jsonp window.postmessage() iframe
jsonp
JSONP(JSON with Padding)是資料格式JSON的一種“使用模式”,可以讓網(wǎng)頁(yè)從別的網(wǎng)域獲取資料。
由于同源策略,一般來(lái)說(shuō)位于server1.example.com的網(wǎng)頁(yè)無(wú)法與 server2.example.com的服務(wù)器溝通,而HTML的 <script>元素是一個(gè)例外。利用 <script>元素的這個(gè)開(kāi)放策略,網(wǎng)頁(yè)可以得到從其他來(lái)源動(dòng)態(tài)產(chǎn)生的JSON資料,而這種使用模式就是所謂的 JSONP。
為了讓瀏覽器可以在 <script>元素運(yùn)行,從src里URL 回傳的必須是可執(zhí)行的JavaScript。在JSONP的使用模式里,該URL回傳的是由函數(shù)調(diào)用包起來(lái)的動(dòng)態(tài)生成JSON,這就是JSONP的 “填充(padding)” 或是 “前輟(prefix)” 的由來(lái)。
使用:
慣例上瀏覽器提供回調(diào)函數(shù)的名稱當(dāng)作送至服務(wù)器的請(qǐng)求中命名查詢參數(shù)的一部分,例如:
<script type="text/javascript" src="http://server2.example.com/RetrieveUser?UserId=1823&jsonp=parseResponse">
</script>
服務(wù)器會(huì)在傳給瀏覽器前將JSON數(shù)據(jù)填充到回調(diào)函數(shù)(parseResponse)中:
parseResponse({"Name": "小明", "Id" : 1823, "Rank": 7})
瀏覽器得到parseResponse函數(shù)里傳入的實(shí)參,就是真實(shí)的數(shù)據(jù)。
安全:
使用遠(yuǎn)程網(wǎng)站的script標(biāo)簽會(huì)讓遠(yuǎn)程網(wǎng)站得以注入任何的內(nèi)容至網(wǎng)站里。如果遠(yuǎn)程的網(wǎng)站有JavaScript注入漏洞,原來(lái)的網(wǎng)站也會(huì)受到影響。
粗略的JSONP部署很容易受到跨站請(qǐng)求偽造(CSRF/XSRF)的攻擊。因?yàn)镠TML <script>標(biāo)簽在瀏覽器里不遵守同源策略,惡意網(wǎng)頁(yè)可以要求并獲取屬于其他網(wǎng)站的JSON資料。這可能泄漏用戶的密碼或是其他敏感資料。不過(guò)當(dāng)JSON資料不涉密、或者可以保證服務(wù)器專有性的情況下,能避免這個(gè)問(wèn)題。
wiki: https://zh.wikipedia.org/wiki/JSONP
另一個(gè)解決跨域這個(gè)問(wèn)題的新方法是跨域資源共享(Cross-origin resource sharing,縮寫(xiě):CORS)。
window.name
在某些框架里(如,SessionVars 和 Dojo's dojox.io.windowName) ,該屬性也被用于作為 JSONP 的一個(gè)更安全的備選,來(lái)提供跨域通信(cross-domain messaging)。
現(xiàn)代 web 應(yīng)用應(yīng)使用 postMessage API 進(jìn)行敏感的跨域通信。

window.postmessage()
window.postMessage() 方法提供了一種受控機(jī)制來(lái)規(guī)避同源策略的限制,只要正確的使用,這種方法就可以很安全地實(shí)現(xiàn)跨源通信。
在窗口上調(diào)用 targetWindow.postMessage() 方法分發(fā)一個(gè) MessageEvent 消息
otherWindow.postMessage(message, targetOrigin, [transfer]);
其他window可以監(jiān)聽(tīng)分發(fā)的message:
window.addEventListener("message", receiveMessage, false);
function receiveMessage(event){
var origin = event.origin
if (origin !== "http://example.org:8080")
return;
// ...
}
安全
當(dāng)您使用postMessage將數(shù)據(jù)發(fā)送到其他窗口時(shí),始終指定精確的目標(biāo)origin,而不是“*”
如果您確實(shí)希望從其他網(wǎng)站接收message,請(qǐng)始終使用origin和source屬性驗(yàn)證發(fā)件人的身份。任何窗口(包括例如http://evil.example.com)都可以向任何其他窗口發(fā)送消息,并且您不能保證未知發(fā)件人不會(huì)發(fā)送惡意消息。無(wú)法檢查origin和source屬性會(huì)導(dǎo)致跨站點(diǎn)腳本攻擊。
驗(yàn)證身份后,您仍然應(yīng)該始終驗(yàn)證接收到的消息的語(yǔ)法。否則,您信任只發(fā)送受信任郵件的網(wǎng)站中的安全漏洞可能會(huì)在您的網(wǎng)站中打開(kāi)跨網(wǎng)站腳本漏洞。
MDN:https://developer.mozilla.org/zh-CN/docs/Web/API/Window/postMessage

iframe
iframe 是 HTML內(nèi)聯(lián)框架元素 (<iframe>), 表示嵌套的browsing context。會(huì)被包含在 window.frames 偽數(shù)組(類數(shù)組的對(duì)象)中。
它能夠?qū)⒘硪粋€(gè)HTML頁(yè)面嵌入到當(dāng)前頁(yè)面中。
每個(gè)嵌入的瀏覽上下文(embedded browsing context)都有自己的會(huì)話歷史記錄(session history)和DOM樹(shù)。
包含嵌入內(nèi)容的瀏覽上下文稱為父級(jí)瀏覽上下文。
頂級(jí)瀏覽上下文(沒(méi)有父級(jí))通常是由 Window 對(duì)象表示的瀏覽器窗口。
頁(yè)面上的每個(gè)iframe都需要增加內(nèi)存和其它計(jì)算資源,這是因?yàn)槊總€(gè)瀏覽上下文都擁有完整的文檔環(huán)境
在框架內(nèi)部,腳本可以通過(guò) window.parent 引用父窗口對(duì)象。
腳本訪問(wèn)框架內(nèi)容必須遵守同源策略,并且無(wú)法訪問(wèn)非同源的 window 對(duì)象的幾乎所有屬性。
同源策略同樣適用于子窗體訪問(wèn)父窗體的 window 對(duì)象。
對(duì)于非同源頁(yè)面,則可以通過(guò)嵌入同源iframe 作為"橋梁",將非同源頁(yè)面通信轉(zhuǎn)換為同源頁(yè)面通信。
跨域通信可以通過(guò) window.postMessage 來(lái)實(shí)現(xiàn)。

MDN:https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/iframe
總結(jié)
廣播模式:
Broadcast Channe Service Worker LocalStorage+StorageEvent
共享存儲(chǔ)模式:
Shared Worker IndexedDB cookie
口口相傳模式:
window.open + window.opener
基于服務(wù)端:
Websocket Comet SSE

讓我們一起攜手同走前端路!

● 工作中常見(jiàn)頁(yè)面布局的n種實(shí)現(xiàn)方法
● 三欄響應(yīng)式布局(左右固寬中間自適應(yīng))的5種方法
● 兩欄自適應(yīng)布局的n種實(shí)現(xiàn)方法匯總
