平時前端都是和後臺聯調,或者在內嵌webview的客戶端上和客戶端聯調,前端和前端聯調是什麼鬼?其實也是存在的,好比另外一個前端寫了一個龐大的模塊(如遊戲、在線ide、可視化編輯頁面等須要沙盒環境的狀況),此時引進來須要使用iframe來使用。在一個大需求裏面,按照模塊化分工的話,顯然iframe裏面的功能由一我的負責,主頁面由另外一我的負責。不一樣的人負責的東西同時展現在頁面上交互,那麼兩個前端開發的過程當中必然有聯調的過程html
背景:父頁面index.html裏面有一個iframe,iframe的src爲子頁面(另外一個html的連接),下文都是基於此狀況下進行前端
// 父頁面的js
document.querySelector("iframe").onload = () => {
window.frames[0].postMessage("data from parent", "*");
};
// 子頁面的js
window.addEventListener(
"message",
e => {
console.log(e); // e是事件對象,e.data便是父頁面發送的message
},
false
);
複製代碼
這個是比較傳統的方法了。注意的是,addEventListener
接收消息的時候,必須首先使用事件對象的origin和source屬性來校驗消息的發送者的身份,若是這裏有差錯,可能會致使跨站點腳本攻擊。並且須要iframe的onload觸發後才能使用postmessagehtml5
低門檻的一種手段,能夠跨域react
父頁面web
const iframe = document.querySelector("iframe");
const { src } = iframe;
// 把數據轉字符串,再經過哈希傳遞到子頁面
function postMessageToIframe(data) {
iframe.src = `${src}#${encodeURIComponent(JSON.stringify(data))}`;
}
複製代碼
子iframe頁面json
window.onhashchange = e => {
// 監聽到哈希變化,序列化json
const data = JSON.parse(decodeURIComponent(location.hash.slice(1)));
console.log(data, "data >>>>");
};
複製代碼
打開父頁面,執行postMessageToIframe({ txt: 'i am lhyt' })
,便可看見控制檯有子頁面的反饋:跨域
反過來,子頁面給父頁面通訊,使用的是parent:數組
// 子頁面
parent.postMessageToIframe({ name: "from child" })
// 父頁面, 代碼是和子頁面同樣的
window.onhashchange = () => {
const data = JSON.parse(decodeURIComponent(location.hash.slice(1)));
console.log(data, "data from child >>>>");
};
複製代碼
注意:瀏覽器
Uncaught DOMException: Blocked a frame with origin "null" from accessing a cross-origin frame.
localstorage
是瀏覽器同域標籤共用的存儲空間。html5支持一個onstorage
事件,咱們在window對象上添加監聽就能夠監聽到變化: window.addEventListener('storage', (e) => console.log(e))安全
須要注意 此事件是非當前頁面對localStorage進行修改時纔會觸發,當前頁面修改localStorage不會觸發監聽函數!!!
// 父頁面
setTimeout(() => {
localStorage.setItem("a", localStorage.getItem("a") + 1);
}, 2000);
window.addEventListener("storage", e => console.log(e, "parent"));
// 子頁面
window.addEventListener("storage", e => console.log(e, "child"));
複製代碼
打印出來的storageEvent是這樣的:
都是兩個頁面,要寫兩分html,有沒有辦法不用寫兩個html呢,只須要一個html呢?實際上是能夠的!
給url加上query
參數或者哈希,表示該頁面是子頁面。若是是父頁面,那麼建立一個iframe,src是本頁面href加上query
參數。父頁面html不須要有什麼其餘標籤,只須要一個script便可
const isIframe = location.search;
if (isIframe) {
// 子頁面
window.addEventListener("storage", e => console.log(e, "child"));
} else {
// 父頁面,建立一個iframe
const iframe = document.createElement("iframe");
iframe.src = location.href + "?a=1";
document.body.appendChild(iframe);
setTimeout(() => {
localStorage.setItem("a", localStorage.getItem("a") + 1);
}, 2000);
window.addEventListener("storage", e => console.log(e, "parent"));
}
複製代碼
MessageChannel
建立一個新的消息通道,並經過它的兩個MessagePort 屬性發送數據,並且在 Web Worker 中可用。MessageChannel
的實例有兩個屬性,portl1
和port2
。給port1發送消息,那麼port2就會收到。
// 父頁面
const channel = new MessageChannel();
// 給子頁面的window注入port2
iframe.contentWindow.port2 = channel.port2;
iframeonload = () => {
// 父頁面使用port1發消息,port2會收到
channel.port1.postMessage({ a: 1 });
};
// 子頁面,使用父頁面注入的port2
window.port2.onmessage = e => {
console.error(e);
};
複製代碼
MessageChannel優勢: 能夠傳對象,不須要手動序列化和反序列化,並且另外一個port收到的是對象深拷貝
是worker的一種,此worker能夠被多個頁面同時使用,能夠從幾個瀏覽上下文中訪問,例如幾個窗口、iframe、worker。它具備不一樣的全局做用域——只有一部分普通winodow下的方法。讓多個頁面共享一個worker,使用該worker做爲媒介,便可實現通訊
worker的代碼
// 存放全部的鏈接端口
const everyPorts = [];
onconnect = function({ ports }) {
// onconnect一觸發,就存放到數組裏面
everyPorts.push(...ports);
// 每次鏈接全部的端口都加上監聽message事件
[...ports].forEach(port => {
port.onmessage = function(event) {
// 每次收到message,對全部的鏈接的端口廣播,除了發消息的那個端口
everyPorts.forEach(singlePort => {
if (port !== singlePort) {
singlePort.postMessage(event.data.data);
}
});
};
});
};
複製代碼
父頁面js代碼
const worker = new SharedWorker("./worker.js");
window.worker = worker;
worker.port.addEventListener(
"message",
e => {
console.log("parent:", e);
},
false
);
worker.port.start();
setTimeout(() => {
worker.port.postMessage({
from: "parent",
data: {
a: 111,
b: 26
}
});
}, 2000);
複製代碼
iframe子頁面的js代碼:
const worker = new SharedWorker("./worker.js");
worker.port.onmessage = function(e) {
console.log("child", e);
};
worker.port.start();
setTimeout(() => {
worker.port.postMessage({ data: [1, 2, 3] });
}, 1000);
複製代碼
正常狀況下,postMessage發生的時機應該是所有內容onload後執行最好,否則對方還沒load完,還沒綁定事件,就沒有收到onmessage了
SharedWorker也是能夠傳對象的哦
上面不少例子,都用了contentWindow,既然contentWindow是iframe本身的window,那麼咱們就能夠隨意注入任何內容,供iframe調用了。前端和客戶端聯調,經常使用的方法之一就是注入函數。子頁面調用父頁面的方法,由於有parent這個全局屬性,那麼父頁面的window也是能夠拿到的了
// 父頁面
document.querySelector("iframe").contentWindow.componentDidMount = () => {
console.log("iframe did mount");
};
// 子頁面
window.onload = () => {
// 假設這裏有react一系列流程運行...
setTimeout(() => {
// 假設如今是react組件didmount的時候
window.componentDidMount && window.componentDidMount();
}, 1000);
};
複製代碼
下面,基於給iframe的window注入方法,來設計一個簡單的通訊模塊
父頁面下,給window掛上parentPageApis對象,是子頁面調用方法的集合。並給子頁面注入一個callParentApi的方法來調父頁面的方法。
const iframe = document.querySelector("iframe");
window.parentPageApis = window.parentPageApis || {};
// 父頁面本身給本身注入子頁面調用的方法
Object.assign(window.parentPageApis, {
childComponentDidMount() {
console.log("子頁面did mount");
},
changeTitle(title) {
document.title = title;
},
showDialog() {
// 彈窗
}
});
// 給子頁面注入一個callParentApi的方法來調父頁面
iframe.contentWindow.callParentApi = function(name, ...args) {
window.parentPageApis[name] && window.parentPageApis[name].apply(null, args);
};
iframe.contentWindow.childPageApis =
iframe.contentWindow.childPageApis || {};
Object.assign(iframe.contentWindow.childPageApis, {
// 父頁面也能夠給子頁面注入方法
});
setTimeout(() => {
// 調用子頁面的方法
callChildApi("log", "父頁面調子頁面的log方法打印");
}, 2000);
複製代碼
子頁面也給父頁面注入callChildApi方法,並把本身的一些對外的方法集合寫在childPageApis上
window.childPageApis = window.childPageApis || {};
Object.assign(window.childPageApis, {
// 子頁面本身給本身注入方法
log(...args) {
console.log(...args);
}
});
window.parent.window.callChildApi = function(name, ...args) {
window.childPageApis[name] && window.childPageApis[name].apply(null, args);
};
window.onload = () => {
// 假設這裏有react一系列流程運行...
setTimeout(() => {
// 假設如今是react組件didmount的時候,告訴父頁面
window.callParentApi("childComponentDidMount");
}, 1000);
};
複製代碼
以上的storage、SharedWorker的方案,也適用於「不一樣tab通訊」這個問題。總的來講,SharedWorker比較安全,注入全局方法比較靈活,哈希變換通訊比較簡單。postmessage、哈希變化、storage事件都是基於字符串,MessageChannel、SharedWorker能夠傳遞任何「可拷貝的值」。全局注入就能夠隨心所欲了,但也是最危險的,須要作好防範
關注公衆號《不同的前端》,以不同的視角學習前端,快速成長,一塊兒把玩最新的技術、探索各類黑科技