JavaScript iframe 多窗口通信实战

生活中,最使人疲惫的往往不是道路的遥远,而是心中的郁闷;最使人痛苦的往往不是生活的不幸,而是希望的破灭;最使人颓废的往往不是前途的坎坷,而是自信的丧失;最使人绝望的往往不是挫折的打击,而是心灵的死亡。所以我们要有自己的梦想,让梦想的星光指引着我们走出落漠,走出惆怅,带着我们走进自己的理想。

导读:本篇文章讲解 JavaScript iframe 多窗口通信实战,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,来源:原文

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 事件。当有其他窗口通过 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}</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)
    }
  })
}

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/137377.html

(0)
飞熊的头像飞熊bm

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!