JavaScript iframe 实现多窗口通信实例详解

这篇文章主要为大家介绍了JavaScript iframe 实现多窗口通信实例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

引言

我最近在完善 easyjobs 代码共享的功能。

左侧是代码编辑器,右侧下方有一个控制台。

当我们在左侧编辑完成代码后,点击运行 JS,右侧的控制台就可以输出内容。

而右侧上方有一个渲染画布,用来作为代码运行的容器。

你可以打开网址尝试:www.easyjobs.biz/code-sharin…

因为同时需要运行 JavaScript 代码,所以需要对环境进行隔离。也就是要有一个独立的 JavaScript 运行环境,也可以叫做沙箱。

该怎么做呢?

实现 JavaScript 沙箱的方案有很多,比如 iframe、with+Proxy、还有基于 Object.freeze 的不成熟提案,如果不涉及 Web API 的话,甚至可以借助 nodejs 的 vm 模块。

不过 JavaScript 沙箱不是本文的重点。我的场景决定了 iframe 是最好的选择,因为我不仅仅需要隔离 JS 代码,还要隔离 HTML 和 CSS 代码。

如何做沙箱呢?

iframe 有一个 srcdoc 属性,把要执行的代码传给它就可以了。

为了方便查看 iframe 中 console 输出的内容,我们还需要想办法接收 iframe 传递过来的消息。

这也就是本文的主要内容,iframe 通信实战。

iframe 基本通信

我在这里用代码来演示一下 iframe 最基本的通信是如何做的。

基本的 HTML 结构

首先我们有一个 index.html 文件。

  父窗口 

父窗口

然后有一个 sub.html。

  子窗口 

子窗口

它们的关系就是相互嵌套的关系。

打开 index.html,大概是下面这样。

需要注意,多窗口通信需要使用 http(s) 协议。

使用 JavaScript 在窗口之间发送消息

我们来实现一下父窗口的 sendMessage 方法。

let sub = window.frames[0] function sendMessage() { sub.postMessage({ msg: "来自父窗口的一条消息" }) } 

其中 window.frames 是获取当前窗口的所有 iframe 元素,它返回一个类似数组的结构。

通过调用 sub 的 postMessage 方法可以传递消息。

然后我们来到 sub.html 中编写接收端的代码。

const responseEl = document.getElementById("response") window.addEventListener("message", function (e) { responseEl.innerHTML += `收到一条消息:${e.data.msg}` }) 

接收端使用 window.addEventListener 来监听 message 事件。当有其他窗口通过 poseMessage 来向当前窗口发送消息时,会触发这个事件。

我们来点击父窗口的「发送一条消息给子窗口」按钮。

可以看到子窗口可以打印父窗口的消息。

同理,我们也可以通过 parent.postMessage 反向向父窗口传递消息。

在 sub.html 中继续增加 sendMessage 代码。

function sendMessage() { parent.postMessage({ msg: "来自子窗口的一条消息" }) } 

这个代码和 index.html 中发送消息的代码很相似,唯一的区别就是接受者变成了 parent。parent 就是指当前窗口的父窗口。

回到 index.html 中,增加监听代码。监听代码与子窗口完全一致,可以直接复制过来。

const responseEl = document.getElementById("response") window.addEventListener("message", function (e) { responseEl.innerHTML += `收到一条消息:${e.data.msg}
` })

我们来点击子窗口的「发送一条消息给父窗口」按钮。

这样就实现了 iframe 窗口间双向通信。

注意事项

类型

需要注意的是,postMessage 仅支持 JSON 支持的类型。

  • string
  • number
  • null
  • boolean
  • object
  • array

如果传递 undefined 的话,会自动转成 null。

除了上述类型以外的其他类型都不支持,比如 function、symbol。如果传递了这些类型,浏览器会报错。

如何传递函数并执行

传递函数是一个很常见的需求,我们可以通过把函数转换为字符串的方式进行传递。

比如下面这样:

function fn () {} sub.postMessage({ fn: fn.toString() }) 

在接收方只需要通过 eval 就可以调用函数字符串了。

不过如果函数内引用了外部变量的话,那就不行了。

比如下面这样:

let name = '代码与野兽' function fn () { console.log(name) } sub.postMessage({ fn: fn.toString() }) 

因为接收端无法获取到发送端的变量。

如果碰巧接收端也存在 name 这个变量的话,eval 在执行时就会访问到接收端的变量而非发送端的变量。

这里也体现出了纯函数的优势。如果我们遵循函数式编程范式编写了纯函数,就不会导致这个问题。

如何在父窗口访问到子窗口的 console

回到文章开头,虽然我们可以通过 iframe 通信来传递消息,但实现 iframe 执行 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) } } 

其中会调用 funcToString 方法,这个方法就是把所有的 function 字符串化。

因为我们不确定传入的结构的嵌套深度,所以需要使用递归来转换。

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 实现多窗口通信实例详解的详细内容,更多关于JavaScript iframe多窗口通信的资料请关注0133技术站其它相关文章!

以上就是JavaScript iframe 实现多窗口通信实例详解的详细内容,更多请关注0133技术站其它相关文章!

赞(0) 打赏
未经允许不得转载:0133技术站首页 » JavaScript 教程