JavaScript iframe 實現(xiàn)多窗口通信實例詳解
引言
我最近在完善 easyjobs 代碼共享的功能。
左側(cè)是代碼編輯器,右側(cè)下方有一個控制臺。
當我們在左側(cè)編輯完成代碼后,點擊運行 JS,右側(cè)的控制臺就可以輸出內(nèi)容。
而右側(cè)上方有一個渲染畫布,用來作為代碼運行的容器。
你可以打開網(wǎng)址嘗試:www.easyjobs.biz/code-sharin…。
因為同時需要運行 JavaScript 代碼,所以需要對環(huán)境進行隔離。也就是要有一個獨立的 JavaScript 運行環(huán)境,也可以叫做沙箱。
該怎么做呢?
實現(xiàn) JavaScript 沙箱的方案有很多,比如 iframe、with+Proxy、還有基于 Object.freeze 的不成熟提案,如果不涉及 Web API 的話,甚至可以借助 nodejs 的 vm 模塊。
不過 JavaScript 沙箱不是本文的重點。我的場景決定了 iframe 是最好的選擇,因為我不僅僅需要隔離 JS 代碼,還要隔離 HTML 和 CSS 代碼。
如何做沙箱呢?
iframe 有一個 srcdoc 屬性,把要執(zhí)行的代碼傳給它就可以了。
<iframe srcdoc="<script>alert('hello')</script>"></iframe>
為了方便查看 iframe 中 console 輸出的內(nèi)容,我們還需要想辦法接收 iframe 傳遞過來的消息。
這也就是本文的主要內(nèi)容,iframe 通信實戰(zhàn)。
iframe 基本通信
我在這里用代碼來演示一下 iframe 最基本的通信是如何做的。
基本的 HTML 結(jié)構(gòu)
首先我們有一個 index.html 文件。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <title>父窗口</title> </head> <body> <p>父窗口</p> <iframe src="./sub.html"></iframe> <button onclick="sendMessage()">發(fā)送一條消息給子窗口</button> <p id="response"></p> </body> </html>
然后有一個 sub.html。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>子窗口</title> </head> <body> <p>子窗口</p> <button onclick="sendMessage()">發(fā)送一條消息給父窗口</button> <p id="response"></p> </body> </html>
它們的關(guān)系就是相互嵌套的關(guān)系。
打開 index.html,大概是下面這樣。
需要注意,多窗口通信需要使用 http(s) 協(xié)議。
使用 JavaScript 在窗口之間發(fā)送消息
我們來實現(xiàn)一下父窗口的 sendMessage 方法。
let sub = window.frames[0] function sendMessage() { sub.postMessage({ msg: "來自父窗口的一條消息" }) }
其中 window.frames 是獲取當前窗口的所有 iframe 元素,它返回一個類似數(shù)組的結(jié)構(gòu)。
通過調(diào)用 sub 的 postMessage 方法可以傳遞消息。
然后我們來到 sub.html 中編寫接收端的代碼。
const responseEl = document.getElementById("response") window.addEventListener("message", function (e) { responseEl.innerHTML += `收到一條消息:${e.data.msg}` })
接收端使用 window.addEventListener 來監(jiān)聽 message 事件。當有其他窗口通過 poseMessage 來向當前窗口發(fā)送消息時,會觸發(fā)這個事件。
我們來點擊父窗口的「發(fā)送一條消息給子窗口」按鈕。
可以看到子窗口可以打印父窗口的消息。
同理,我們也可以通過 parent.postMessage 反向向父窗口傳遞消息。
在 sub.html 中繼續(xù)增加 sendMessage 代碼。
function sendMessage() { parent.postMessage({ msg: "來自子窗口的一條消息" }) }
這個代碼和 index.html 中發(fā)送消息的代碼很相似,唯一的區(qū)別就是接受者變成了 parent。parent 就是指當前窗口的父窗口。
回到 index.html 中,增加監(jiān)聽代碼。監(jiān)聽代碼與子窗口完全一致,可以直接復制過來。
const responseEl = document.getElementById("response") window.addEventListener("message", function (e) { responseEl.innerHTML += `收到一條消息:${e.data.msg}</br>` })
我們來點擊子窗口的「發(fā)送一條消息給父窗口」按鈕。
這樣就實現(xiàn)了 iframe 窗口間雙向通信。
注意事項
類型
需要注意的是,postMessage 僅支持 JSON 支持的類型。
- string
- number
- null
- boolean
- object
- array
如果傳遞 undefined 的話,會自動轉(zhuǎn)成 null。
除了上述類型以外的其他類型都不支持,比如 function、symbol。如果傳遞了這些類型,瀏覽器會報錯。
如何傳遞函數(shù)并執(zhí)行
傳遞函數(shù)是一個很常見的需求,我們可以通過把函數(shù)轉(zhuǎn)換為字符串的方式進行傳遞。
比如下面這樣:
function fn () {} sub.postMessage({ fn: fn.toString() })
在接收方只需要通過 eval 就可以調(diào)用函數(shù)字符串了。
不過如果函數(shù)內(nèi)引用了外部變量的話,那就不行了。
比如下面這樣:
let name = '代碼與野獸' function fn () { console.log(name) } sub.postMessage({ fn: fn.toString() })
因為接收端無法獲取到發(fā)送端的變量。
如果碰巧接收端也存在 name 這個變量的話,eval 在執(zhí)行時就會訪問到接收端的變量而非發(fā)送端的變量。
這里也體現(xiàn)出了純函數(shù)的優(yōu)勢。如果我們遵循函數(shù)式編程范式編寫了純函數(shù),就不會導致這個問題。
如何在父窗口訪問到子窗口的 console
回到文章開頭,雖然我們可以通過 iframe 通信來傳遞消息,但實現(xiàn) iframe 執(zhí)行 console 同步到父窗口,仍然是個問題。
其實非常簡單,把 console 對象上的所有方法劫持,然后把這段代碼加入到 iframe 最頂部就可以了。
var fns = new Map() for(let key in console) { fns.set(key, console[key]) console[key] = (...args) => { funcToString(args) window.parent.postMessage({ type: 'console.' + key, args }, "*") return fns.get(key)(...args) } }
其中會調(diào)用 funcToString 方法,這個方法就是把所有的 function 字符串化。
因為我們不確定傳入的結(jié)構(gòu)的嵌套深度,所以需要使用遞歸來轉(zhuǎn)換。
function funcToString(args) { Object.keys(args).forEach((key) => { const arg = args[key] if (typeof arg === "function") { args[key] = arg.toString() } else if (typeof arg === "object") { funcToString(arg) } }) }
以上就是JavaScript iframe 實現(xiàn)多窗口通信實例詳解的詳細內(nèi)容,更多關(guān)于JavaScript iframe多窗口通信的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
如何在JavaScript中實現(xiàn)私有屬性的寫類方式(一)
這篇文章主要介紹了如何在JavaScript中實現(xiàn)私有屬性的寫類方式。需要的朋友可以過來參考下,希望對大家有所幫助2013-12-12js中繼承的幾種用法總結(jié)(apply,call,prototype)
本篇文章主要介紹了js中繼承的幾種用法總結(jié)(apply,call,prototype) 需要的朋友可以過來參考下,希望對大家有所幫助2013-12-12js設(shè)置文本框中焦點位置在最后的示例代碼(簡單實用)
本篇文章主要是對js設(shè)置文本框中焦點位置在最后的示例代碼進行了介紹,需要的朋友可以過來參考下,希望對大家有所幫助2014-03-03react-router-dom?v6?通過outlet實現(xiàn)keepAlive?功能的實現(xiàn)
本文主要介紹了react-router-dom?v6?通過outlet實現(xiàn)keepAlive功能,文中根據(jù)實例編碼詳細介紹的十分詳盡,具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-03-03js字符串替換所有的指定字符或文字(推薦replaceAll方法)
要實現(xiàn)js字符串替換所有的某個字符,推薦大家使用replaceAll方法,默認不是所有瀏覽器都兼容,所以這里給出一個解決方案,需要的朋友可以參考下2014-07-07lhgcalendar時間插件限制只能選擇三個月的實現(xiàn)方法
下面小編就為大家?guī)硪黄猯hgcalendar時間插件限制只能選擇三個月的實現(xiàn)方法。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-07-07詳解JavaScript原生封裝ajax請求和Jquery中的ajax請求
在本篇文章中我們總結(jié)了關(guān)于JavaScript原生封裝ajax請求和Jquery中的ajax請求的知識點內(nèi)容,需要的朋友們學習參考下。2019-02-02