欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

JavaScript iframe 實現(xiàn)多窗口通信實例詳解

 更新時間:2022年10月08日 09:31:06   作者:代碼與野獸  
這篇文章主要為大家介紹了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)文章

最新評論