跨域通信是前端開(kāi)發(fā)中經(jīng)常會(huì)遇到的情景,跨域通信有多種多樣的方式,今天就詳細(xì)說(shuō)一下使用 postMessage 這樣方式進(jìn)行跨頁(yè)面腳本的數(shù)據(jù)通信。
一、認(rèn)識(shí) window.postMessage
根據(jù) MDN 的官方文檔解釋:window.postMessage() 方法可以安全地實(shí)現(xià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ǔ)法
1、向目標(biāo)窗口發(fā)送消息
otherWindow.postMessage(message,targetOrigin)
- otherWindow
其他窗口的一個(gè)引用,比如 iframe 的 contentWindow 屬性、執(zhí)行 window.open 返回的窗口對(duì)象、或者是命名過(guò)或數(shù)值索引的 window.frames。
- message
將要發(fā)送到其他窗口的數(shù)據(jù)。
- targetOrigin
指定哪些窗口能接收到消息,其值可以是字符串"*"(表示無(wú)限制)或者一個(gè) URI,在發(fā)送消息的時(shí)候,如果目標(biāo)窗口的協(xié)議、主機(jī)地址或端口這三者的任意一項(xiàng)不匹配 targetOrigin 提供的值,那么消息就不會(huì)被發(fā)送;只有三者完全匹配,消息才會(huì)被發(fā)送。這個(gè)機(jī)制用來(lái)控制消息可以發(fā)送到哪些窗口.
2、目標(biāo)窗口接收消息
window.addEventListener("message", receiveMessage, false);
function receiveMessage(event){
// event.origin 表示消息的來(lái)源地址
// event.data 表示接收到的數(shù)據(jù)
if (event.origin !== 'http: //www.xxxx.com') {
return
} else {
console.log(event.data)
}
}
三、具體案例
父窗口主動(dòng)給子窗口發(fā)消息
本案例是在 Vue2.0+iView3.0 的環(huán)境下進(jìn)行,在一個(gè)彈框內(nèi)嵌套一個(gè) iframe 頁(yè)面。
-
模板
<button @click="show">顯示</button> <Modal v-model="isModalShow" width="80%" class-name="select_rules_modal" footer-hide @on-visible-change="visibleModalChange" > <div class="rules_header">iFrame頁(yè)面</div> <div class="rules_body"> <iframe class="rules_iframe" src="http://www.target.com:9900/list" id="iframe" ref="iframe" v-if="isModalShow" ></iframe> </div> </Modal> -
邏輯
data() { return { isModalShow: false } }, methods: { show(){ this.isModalShow = true let iframe = document.getElementById('iframe') let data = 'hello' if (iframe.attachEvent){ // 兼容IE寫(xiě)法 // 必須在ifame頁(yè)面加載完畢后才能發(fā)送消息,否則對(duì)方接收不到 iframe.attachEvent("onload", function(){ // 發(fā)消息 iframe.contentWindow.postMessage(data, 'http://target.com:9900') }) } else { iframe.onload = function(){ // 發(fā)消息 iframe.contentWindow.postMessage(data, 'http://target.com:9900') } } // 收消息(在目標(biāo)頁(yè)面進(jìn)行相關(guān)操作,接收發(fā)送過(guò)來(lái)需要的數(shù)據(jù)) window.addEventListener('message',this.receiveMessage,false) }, // 接收消息回調(diào) receiveMessage(event){ // 判斷是否是目標(biāo)地址發(fā)過(guò)來(lái)的消息,否則不接收 if (event.origin !== 'http://target.com:9900') { return } else { // event.data 為接收到的數(shù)據(jù) console.log(event.data) // 處理數(shù)據(jù)..... this.isModalShow = false } }, // 彈框關(guān)閉時(shí)移除消息監(jiān)聽(tīng) visibleModalChange(){ window.removeEventListener('message',this.receiveMessage,false) } }子窗口主動(dòng)給父窗口發(fā)送消息
思路: 子窗口無(wú)法直接給父窗口發(fā)消息,因?yàn)榇嬖诳缬騿?wèn)題,如果要實(shí)現(xiàn)子窗口給父窗口發(fā)送消息,需要父窗口先給子窗口發(fā)送一個(gè)消息,子窗口記住事件源,然后才可以實(shí)現(xiàn)發(fā)送消息給父窗口。
1、父窗口在 iframe 加載完后發(fā)送消息,并監(jiān)聽(tīng)子窗口的消息let iframe = document.getElementById("iframe"); if (iframe.attachEvent) { // 兼容IE寫(xiě)法 iframe.attachEvent("onload", function () { // 發(fā)消息 iframe.contentWindow.postMessage(data, "https://yyy.com"); }); } else { iframe.onload = function () { // 發(fā)消息 iframe.contentWindow.postMessage(data, "https://yyy.com"); }; } // 收消息 window.addEventListener("message", this.receiveChildMessage, false); ....... receiveChildMessage(event) { //....... }2、子窗口在頁(yè)面加載完畢后監(jiān)聽(tīng)消息,收到消息后保存事件源
window.addEventListener("message", this.receiveParentMsg, false); ..... receiveParentMsg(event) { if (event.origin !== 'http://xxx.com'){ return } // 存儲(chǔ)事件源,以便于保存時(shí)發(fā)消息 this.parentEvent = event },3、事件觸發(fā)時(shí)給父窗口發(fā)送消息
this.parentEvent.source.postMessage("發(fā)送消息", this.parentEvent.origin);4、頁(yè)面銷毀時(shí)移除消息監(jiān)聽(tīng)
window.removeEventListener("message", this.receiveParentMsg, false);四、注意事項(xiàng)
1、需要通過(guò)
onload的事件監(jiān)聽(tīng)iframe頁(yè)面加載完畢后在發(fā)生消息,發(fā)送接收不到。2、一定要在頁(yè)面關(guān)閉的時(shí)候移除監(jiān)聽(tīng)事件,否則會(huì)導(dǎo)致在每打開(kāi)一次
iframe頁(yè)面,接收的消息會(huì)疊加一次的問(wèn)題。3、如果是父窗口 iframe 加載完后發(fā)消息,子窗口頁(yè)面加載后監(jiān)聽(tīng)消息,一定要注意子窗口監(jiān)聽(tīng)消息要在父窗口發(fā)消息以前,否則可能收不到消息,放在哪個(gè)生命周期中根據(jù)自己情況處理。