JavaScript iframe 多窗口通信实战:从基础到控制台劫持

作者:纸飞机时间:2026-10-25 21:14:11浏览:590

本文详解 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 多窗口通信的资料请关注本站其它相关文章!

作者最新文章