本文详解 JavaScript 中 iframe 多窗口通信的实现方法。通过 postMessage API 实现父子窗口双向消息传递,深入探讨数据类型限制及函数传递的局限性。重点介绍如何劫持子窗口 console 对象,利用递归字符串化将执行日志同步至父窗口,为代码沙箱环境提供完整的调试支持。
引言
近期我在优化 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 srcdoc="<script>alert('hello')</script>"></iframe>
为便于查看 iframe 中 console 的输出内容,还需解决接收 iframe 传递消息的问题。
这正是本文的重点:iframe 通信实战。
iframe 基本通信
此处通过代码演示 iframe 最基本的通信实现方式。
基本的 HTML 结构
首先准备一个 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()">发送一条消息给子窗口</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()">发送一条消息给父窗口</button>
<p id="response"></p>
</body>
</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 事件。当其他窗口通过 postMessage 向当前窗口发送消息时,将触发此事件。
点击父窗口的「发送一条消息给子窗口」按钮。

可以看到子窗口成功打印了父窗口的消息。
同理,也可通过 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}</br>`
})
点击子窗口的「发送一条消息给父窗口」按钮。

至此实现了 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 多窗口通信的资料请关注本站其它相关文章!