window.postMessage 详细讲解

什么是 window.postMessage

由于浏览器同源策略的限制,通常情况下,两个不同的页面,需要同时满足协议、端口、主机三者相同,才可以相互通信,否则就会出现跨域的情况。window.postMessage() 方法可以安全地实现跨源通信。它提供了一种受控机制来规避此限制,只要正确的使用,这种方法就很安全。

怎么使用 window.postMessage

JavaScript 复制代码
otherWindow.postMessage(message, targetOrigin, [transfer]);
  • otherWindow:目标窗口的一个引用,比如 iframe 的 contentWindow 属性、执行window.open返回的窗口对象;
  • message:将要发送到其他 window 的数据。类型可以是字符串,也可以是对象;
  • targetOrigin:消息可以发送到哪些窗口的路径地址,其值可以是字符串"*"(表示无限制)或者一个 URI。

注意:在发送消息的时候,如果目标窗口的协议、主机地址和端口这三者的任意一项不匹配 targetOrigin 提供的值,那么消息就不会被发送;只有三者完全匹配,消息才会被发送。这个机制用来控制消息可以发送到哪些窗口;例如,当用 postMessage 传送密码时,这个参数就显得尤为重要,必须保证它的值与这条包含密码的信息的预期接受者的 origin 属性完全一致,来防止密码被恶意的第三方截获。*如果明确的知道消息应该发送到哪个窗口,那么请始终提供一个有确切值的 targetOrigin,而不是 。不提供确切的目标将导致数据泄露到任何对数据感兴趣的恶意站点。

  • transfer「可选」:(不怎么经常使用)

window.postMessage 的使用场景

演示环境:在 vscode 中,用 Live Server 插件分别启动两个服务,每个服务的端口不一样,可以模拟非同源的环境,每个页面是一个独立的服务,每个服务会在浏览器中代表一个窗口。

现在,就模拟A窗口( http://127.0.0.1:5500/post-message/A.html )和 B窗口( http://127.0.0.1:5501/src/examples/B.html )之间的通信情况。

A窗口给自己传输信息

HTML 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <div>A页面</div>
    <button onclick="sendMessage()">Send Message</button>

    <!-- 点击Send Message 按钮,将会显示下面内容-->
    <div>
        <div id='span1'></div>
        <div id='span2'></div>
        <div id='span3'></div>
    </div>

    <script>   
        function sendMessage() {
            const message = 'Info from A';
            window.postMessage(message, 'http://127.0.0.1:5500');
        }

        // 监听 message 的变化
        window.addEventListener('message', (e) => {
            document.getElementById('span1').innerHTML = e.data // 作为第一个参数传递给postMessage的数据
            document.getElementById('span2').innerHTML = e.origin // 发送消息的来源,可以根据origin来确保期望的发送者
            document.getElementById('span3').innerHTML = e.source // 发送消息的window代理对象
        })
    </script>
</body>
</html>

效果:

A 窗口打开 B 窗口,B 发送信息给 A

A页面:

HTML 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <div>A页面</div>
    <button onclick="sendMessage()">Send Message</button>

    <!-- 点击Send Message 按钮,将会显示下面内容-->
    <div>
        <div id='span1'></div>
        <div id='span2'></div>
        <div id='span3'></div>
    </div>

    <script>   
        function sendMessage() {
            let open = window.open('http://127.0.0.1:5501/src/examples/B.html', 'B');
        }

        // 监听 message 的变化
        window.addEventListener('message', (e) => {
            if (e.origin === 'http://127.0.0.1:5501') { // (安全判断)接收来自 http://127.0.0.1:5501 网页的信息
                document.getElementById('span1').innerHTML = e.data // 作为第一个参数传递给postMessage的数据
                document.getElementById('span2').innerHTML = e.origin // 发送消息的来源,可以根据origin来确保期望的发送者
                document.getElementById('span3').innerHTML = e.source // 发送消息的window代理对象
            }
        })
    </script>
</body>
</html>

B 页面:

HTML 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <div>B页面</div>

    <script>
        let opener = window.opener;
        // 只有 http://127.0.0.1:5500 网页打开的页面,才可以收到来自 B 页面的信息
        opener.postMessage('Info in B', 'http://127.0.0.1:5500');  
    </script>
</body>
</html>

效果:

A 窗口打开 B 窗口,A 发送信息给 B

A页面:

HTML 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <div>A页面</div>
    <button onclick="sendMessage()">Send Message</button>

    <!-- 点击Send Message 按钮,将会显示下面内容-->
    <div>
        <div id='span1'></div>
        <div id='span2'></div>
        <div id='span3'></div>
    </div>

    <script>   
        function sendMessage() {
            let open = window.open('http://127.0.0.1:5501/src/examples/B.html', 'B');
            let message = 'Info in A';
            open.postMessage(message, 'http://127.0.0.1:5501');
        }
    </script>
</body>
</html>

B页面:

HTML 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <div>B页面</div>
    <div>
        <div id='span1'></div>
        <div id='span2'></div>
        <div id='span3'></div>
    </div>

    <script> 
        // 监听 message 的变化
        window.addEventListener('message', (e) => {
            if (e.origin === 'http://127.0.0.1:5500') { // 接收来自 http://127.0.0.1:5500 网页的信息
                document.getElementById('span1').innerHTML = e.data // 作为第一个参数传递给postMessage的数据
                document.getElementById('span2').innerHTML = e.origin // 发送消息的来源,可以根据origin来确保期望的发送者
                document.getElementById('span3').innerHTML = e.source // 发送消息的window代理对象
            }
        })
    </script>
</body>
</html>

效果:当通过 A 页面打开 B 页面的时候,其实在 B 页面闪现一下,然后就没有了(过程很快,建议自己 codding 试一下,这里就不截图了)。分析这个效果:

引用MDN的一句话:

Window 接口的 open() 方法将 URL 作为参数,并将其识别的资源加载到新的或现有的标签页或窗口中。 请注意,远程 URL 不会立即加载。当 window.open() 返回时,窗口总是包含 about:blank。URL 的实际获取是延迟进行的,并在当前脚本块执行完毕后开始。窗口创建和引用资源的加载是异步进行的。

也就是说,A 中的数据已经传输到 B 中,但此时 B 页面可能还没有被加载出来。那就把 postMessage放在一个宏任务中吧,保证调用栈中 windows.open()中的 url 被加载好之后,再去执行 postMessage 中的逻辑。

看下面代码:

A页面:

HTML 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <div>A页面</div>
    <button onclick="sendMessage()">Send Message</button>

    <!-- 点击Send Message 按钮,将会显示下面内容-->
    <div>
        <div id='span1'></div>
        <div id='span2'></div>
        <div id='span3'></div>
    </div>

    <script>   
        function sendMessage() {
            let open = window.open('http://127.0.0.1:5501/src/examples/B.html', 'B');
            let message = 'Info in A';
            setTimeout(() => { // 新增代码
                open.postMessage(message, 'http://127.0.0.1:5501');
            }, 1000)
        }
    </script>
</body>
</html>

此时,B 页面就可以接收到数据了。

当然,也有其他方法,思路就是:我们的目标就是A 窗口打开 B 窗口,A 发送信息给 B。那么,只要做到,A 打开 B(此时还不传输数据),然后 B 给 A 一个反馈,然后 A 再把信息传输给 B即可。代码如下:

A页面:

HTML 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <div>A页面</div>
    <button onclick="sendMessage()">Send Message</button>

    <div>
        <div id='span1'></div>
        <div id='span2'></div>
        <div id='span3'></div>
    </div>

    <script>
        let copyOpen = null;
        function sendMessage() {
            copyOpen = window.open('http://127.0.0.1:5501/src/examples/B.html', 'B');
        }

        // 监听 message 的变化
        window.addEventListener('message', (e) => {
            if (e.origin === 'http://127.0.0.1:5501') { // 接收来自 http://127.0.0.1:5501 网页的信息
                copyOpen.postMessage('Info in A', 'http://127.0.0.1:5501');  // 此时才给 B 传输信息

                document.getElementById('span1').innerHTML = e.data // 作为第一个参数传递给postMessage的数据
                document.getElementById('span2').innerHTML = e.origin // 发送消息的来源,可以根据origin来确保期望的发送者
                document.getElementById('span3').innerHTML = e.source // 发送消息的window代理对象          
            }
        })
    </script>
</body>
</html>

B页面:

HTML 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <div>B页面</div>
    <div>
        <div id='span1'></div>
        <div id='span2'></div>
        <div id='span3'></div>
    </div>

    <script> 
        let opener = window.opener;
        opener.postMessage('来自 B 的反馈' ,'http://127.0.0.1:5500');
        
        // 监听 message 的变化
        window.addEventListener('message', (e) => {
            if (e.origin === 'http://127.0.0.1:5500') { // 接收来自 http://127.0.0.1:5500 网页的信息
                document.getElementById('span1').innerHTML = e.data // 作为第一个参数传递给postMessage的数据
                document.getElementById('span2').innerHTML = e.origin // 发送消息的来源,可以根据origin来确保期望的发送者
                document.getElementById('span3').innerHTML = e.source // 发送消息的window代理对象
            }
        })
    </script>
</body>
</html>

效果图:

最后

码字不易,大家可以点赞、收藏、加关注~

(下期预告,在Iframe里使用window.postMessage)

相关推荐
腾讯TNTWeb前端团队3 小时前
helux v5 发布了,像pinia一样优雅地管理你的react状态吧
前端·javascript·react.js
范文杰6 小时前
AI 时代如何更高效开发前端组件?21st.dev 给了一种答案
前端·ai编程
拉不动的猪6 小时前
刷刷题50(常见的js数据通信与渲染问题)
前端·javascript·面试
拉不动的猪6 小时前
JS多线程Webworks中的几种实战场景演示
前端·javascript·面试
FreeCultureBoy7 小时前
macOS 命令行 原生挂载 webdav 方法
前端
uhakadotcom8 小时前
Astro 框架:快速构建内容驱动型网站的利器
前端·javascript·面试
uhakadotcom8 小时前
了解Nest.js和Next.js:如何选择合适的框架
前端·javascript·面试
uhakadotcom8 小时前
React与Next.js:基础知识及应用场景
前端·面试·github
uhakadotcom8 小时前
Remix 框架:性能与易用性的完美结合
前端·javascript·面试
uhakadotcom8 小时前
Node.js 包管理器:npm vs pnpm
前端·javascript·面试