前端跨页面通信的几种方式

在这里插入图片描述



同源页面通信

Broadcast Channel 【广播】

同一来源的不同文档(在不同的窗口、选项卡、框架或 iframe 中)之间进行通信

// page1

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>page1</title>
</head>
<body>
    <button onclick="sendMessage()">发广播</button>
    <button onclick="closeBroadcast()">关闭广播</button>
    <button onclick="openBroadcast()">开启广播</button>
</body>
</html>

<script>
    let bc = null;

    // 开启广播
    function openBroadcast() {
        console.log('开启广播');
        bc = new BroadcastChannel('cctv');

        // 接收广播
        bc.onmessage = function(e) {
            alert(e.data);
        };

        // 异常处理
        bc.onmessageerror = function(e) {
            console.warn('onmessageerror');
        };
    }
    openBroadcast();

    // 关闭广播
    function closeBroadcast() {
        console.log('关闭广播');
        bc.close();
    }

    // 发消息
    function sendMessage() {
        bc.postMessage('page1: 咯咯')
    }
</script>
// page2

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>page2</title>
</head>
<body>
    <button onclick="sendMessage()">发广播</button>
    <button onclick="closeBroadcast()">关闭广播</button>
    <button onclick="openBroadcast()">开启广播</button>
</body>
</html>

<script>
    let bc = null;

    // 开启广播
    function openBroadcast() {
        console.log('开启广播');
        bc = new BroadcastChannel('cctv');

        // 接收广播
        bc.onmessage = function(e) {
            alert(e.data);
        };

        // 异常处理
        bc.onmessageerror = function(e) {
            console.warn('onmessageerror');
        };
    }
    openBroadcast();

    // 关闭广播
    function closeBroadcast() {
        console.log('关闭广播');
        bc.close();
    }

    // 发消息
    function sendMessage() {
        bc.postMessage('page2: 哒~')
    }
</script>

Service Worker 【广播】

// page1

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>page1</title>
</head>
<body>
    <button onclick="sendMessage()">发广播</button>
    <p></p>
</body>
</html>

<script>
    navigator.serviceWorker.register('sw.js').then(function () {
        console.log('Service Worker 注册成功');
    })

    navigator.serviceWorker.addEventListener('message', function (e) {
        const { data } = e;

        // 过滤自己发送的消息
        if (data.from !== 'page1') {
            document.querySelector('p').innerText = `${data.from}${data.data}`;
        }
    });

    function sendMessage() {
        const msg = {
            from: 'page1',
            data: '咯咯'
        }
        navigator.serviceWorker.controller.postMessage(msg);
    }
</script>
// page2

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>page2</title>
</head>
<body>
    <button onclick="sendMessage()">发广播</button>
    <p></p>
</body>
</html>

<script>
    navigator.serviceWorker.register('sw.js').then(function () {
        console.log('Service Worker 注册成功');
    })

    navigator.serviceWorker.addEventListener('message', function (e) {
        const { data } = e;

        // 过滤自己发送的消息
        if (data.from !== 'page2') {
            document.querySelector('p').innerText = `${data.from}${data.data}`;
        }
    });

    function sendMessage() {
        const msg = {
            from: 'page2',
            data: '哒~'
        }
        navigator.serviceWorker.controller.postMessage(msg);
    }
</script>
// sw.js

self.addEventListener('message', function (e) {
    console.log('service worker receive message', e.data);
    e.waitUntil(
        self.clients.matchAll().then(function (clients) {
            if (!clients || clients.length === 0) {
                return;
            }
            clients.forEach(function (client) {
                client.postMessage(e.data);
            });
        })
    );
});

localStorage 【共享存储 + 监听】

// page1

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>page1</title>
</head>
<body>
    <button onclick="sendMessage()">发广播</button>
    <button onclick="closeBroadcast()">关闭广播</button>
    <button onclick="openBroadcast()">开启广播</button>
</body>
</html>

<script>
    function handleMessage(e) {
        if (e.key === 'localMessage') {
            const msg = JSON.parse(e.newValue);
            alert(msg.data);
        }
    }

    // 开启广播
    function openBroadcast() {
        console.log('开启广播');

        // 在当前页面setItem,storage事件不会触发,另外开个标签页,可以在另一个标签页中触发
        window.addEventListener('storage', handleMessage);
    }
    openBroadcast();

    // 关闭广播
    function closeBroadcast() {
        console.log('关闭广播');
        window.removeEventListener('storage', handleMessage)
    }

    // 发消息
    function sendMessage() {
        const msg = {
            data: 'page1: 咯咯',
            stamp: new Date()
        }

        // 设置相同的内容,storage不会触发,用时间戳解决
        window.localStorage.setItem('localMessage', JSON.stringify(msg));
    }
</script>
<!DOCTYPE html>
// page2

<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>page2</title>
</head>
<body>
    <button onclick="sendMessage()">发广播</button>
    <button onclick="closeBroadcast()">关闭广播</button>
    <button onclick="openBroadcast()">开启广播</button>
</body>
</html>

<script>
    function handleMessage(e) {
        if (e.key === 'localMessage') {
            const msg = JSON.parse(e.newValue);
            alert(msg.data);
        }
    }

    // 开启广播
    function openBroadcast() {
        console.log('开启广播');

        // 在当前页面setItem,storage事件不会触发,另外开个标签页,可以在另一个标签页中触发
        window.addEventListener('storage', handleMessage);
    }
    openBroadcast();

    // 关闭广播
    function closeBroadcast() {
        console.log('关闭广播');
        window.removeEventListener('storage', handleMessage)
    }

    // 发消息
    function sendMessage() {
        const msg = {
            data: 'page2: 哒~',
            stamp: new Date()
        }

        // 设置相同的内容,storage不会触发,用时间戳解决
        window.localStorage.setItem('localMessage', JSON.stringify(msg));
    }
</script>

Shared Worker 【共享存储 + 轮询】

无法主动通知,需要轮询获取最新数据

// page1

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>page1</title>
</head>
<body>
<button onclick="sendMessage()">发广播</button>
<button onclick="closeBroadcast()">关闭广播</button>
<button onclick="openBroadcast()">开启广播</button>
</body>
</html>

<script>
    let worker = null;
    let timer = null;

    // 开启广播
    function openBroadcast() {
        console.log('开启广播');
        worker = new SharedWorker("sharedWorker.js", 'messageWorker'); // [https://developer.mozilla.org/zh-CN/docs/Web/API/SharedWorker/SharedWorker]

        // 定时轮询,发送 get 指令的消息
        timer = setInterval(function () {
            worker.port.postMessage({ get: true });
        }, 1000);

        // 监听 get 消息的返回数据
        worker.port.onmessage = function (e) {
            console.log(e.data);
        }
    }
    openBroadcast();

    function closeBroadcast() {
        console.log('关闭广播');
        clearInterval(timer);
    }

    // 发消息
    function sendMessage() {
        worker.port.postMessage('page1: 咯咯');
    }
</script>
// page2

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>page2</title>
</head>
<body>
    <button onclick="sendMessage()">发广播</button>
    <button onclick="closeBroadcast()">关闭广播</button>
    <button onclick="openBroadcast()">开启广播</button>
</body>
</html>

<script>
    let worker = null;
    let timer = null;

    // 开启广播
    function openBroadcast() {
        console.log('开启广播');
        worker = new SharedWorker("sharedWorker.js", 'messageWorker'); // [https://developer.mozilla.org/zh-CN/docs/Web/API/SharedWorker/SharedWorker]

        // 定时轮询,发送 get 指令的消息
        timer = setInterval(function () {
            worker.port.postMessage({ get: true });
        }, 1000);

        // 监听 get 消息的返回数据
        worker.port.addEventListener('message', (e) => {
            console.log(e.data);
        }, false);

        // addEventListener监听时要手动开始,onmessage时不需要
        worker.port.start();
    }
    openBroadcast();

    function closeBroadcast() {
        console.log('关闭广播');
        clearInterval(timer);
    }

    // 发消息
    function sendMessage() {
        worker.port.postMessage('page2: 哒~');
    }
</script>
// sharedWorker.js
let data = null;

self.addEventListener('connect', function (e) {
    const port = e.ports[0];

    port.addEventListener('message', function (event) {
        // get 指令则返回存储的消息数据
        if (event.data.get) {
            data && port.postMessage(data);
        }
        // 非 get 指令则存储该消息数据
        else {
            data = event.data;
        }
    });

    port.start();
});

window.opener 【口口相传】

// page1

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>page1</title>
</head>
<body>
    <button onclick="sendMessage()">发广播</button>
    <button onclick="closeBroadcast()">关闭广播</button>
    <button onclick="openBroadcast()">开启广播</button>
</body>
</html>

<script>
    let wins = []; // 记录打开的子窗口

    const openedWin = window.open('page2.html')
    wins.push(openedWin);

    // 发广播
    function sendMessage() {
        const openingWins = wins.filter(item => !item.closed); // 过滤已被关闭的页面

        // 向子级窗口发送消息
        if (openingWins.length) {
            const msg = {
                data: 'page1: 咯咯',
                upMsg: false,         // 是否向上级窗口发送的消息
            }

            openingWins.forEach(item => item.postMessage(msg))
        }

        // 有父级窗口,向父级窗口发消息
        if (window.opener && !window.opener.closed) {
            const msg = {
                data: 'page1: 咯咯',
                upMsg: true,         // 是否向上级窗口发送的消息
            }

            window.opener.postMessage(msg);
        }
    }

    // 开启广播
    function openBroadcast() {
        window.addEventListener('message', function (e) {
            const { data, upMsg } = e.data;
            alert(data);

            // 收到向上传递的消息,有父级窗口,继续向上传递
            if (window.opener && !window.opener.closed && upMsg) {
                window.opener.postMessage(e.data);
            }

            const openingWins = wins.filter(item => !item.closed); // 过滤已被关闭的页面

            // 收到向下传递的消息,有子级窗口,继续向下传递
            if (openingWins.length && !upMsg) {
                openingWins.forEach(item => item.postMessage(e.data))
            }
        });
    }
    openBroadcast()

    // 关闭广播
    function closeBroadcast() {
        // TODO
    }
</script>
// page2

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>page2</title>
</head>
<body>
    <button onclick="sendMessage()">发广播</button>
    <button onclick="closeBroadcast()">关闭广播</button>
    <button onclick="openBroadcast()">开启广播</button>
</body>
</html>

<script>
    let wins = []; // 记录打开的子窗口

    const openedWin = window.open('page3.html')
    wins.push(openedWin);

    // 发广播
    function sendMessage() {
        const openingWins = wins.filter(item => !item.closed); // 过滤已被关闭的页面

        // 向子级窗口发送消息
        if (openingWins.length) {
            const msg = {
                data: 'page2: 咯咯',
                upMsg: false,         // 是否向上级窗口发送的消息
            }

            openingWins.forEach(item => item.postMessage(msg))
        }

        // 有父级窗口,向父级窗口发消息
        if (window.opener && !window.opener.closed) {
            const msg = {
                data: 'page2: 咯咯',
                upMsg: true,         // 是否向上级窗口发送的消息
            }

            window.opener.postMessage(msg);
        }
    }

    function openBroadcast() {
        window.addEventListener('message', function (e) {
            const { data, upMsg } = e.data;
            alert(data);


            // 收到向上传递的消息,有父级窗口,继续向上传递
            if (window.opener && !window.opener.closed && upMsg) {
                window.opener.postMessage(e.data);
            }

            const openingWins = wins.filter(item => !item.closed); // 过滤已被关闭的页面

            // 收到向下传递的消息,有子级窗口,继续向下传递
            if (openingWins.length && !upMsg) {
                openingWins.forEach(item => item.postMessage(e.data))
            }
        });
    }
    openBroadcast()

    // 关闭广播
    function closeBroadcast() {
        // TODO
    }
</script>
// page3

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>page3</title>
</head>
<body>
    <button onclick="sendMessage()">发广播</button>
    <button onclick="closeBroadcast()">关闭广播</button>
    <button onclick="openBroadcast()">开启广播</button>
</body>
</html>

<script>
    let wins = []; // 记录打开的子窗口

    // 发广播
    function sendMessage() {
        const openingWins = wins.filter(item => !item.closed); // 过滤已被关闭的页面

        // 向子级窗口发送消息
        if (openingWins.length) {
            const msg = {
                data: 'page3: 咯咯',
                upMsg: false,         // 是否向上级窗口发送的消息
            }

            openingWins.forEach(item => item.postMessage(msg))
        }

        // 有父级窗口,向父级窗口发消息
        if (window.opener && !window.opener.closed) {
            const msg = {
                data: 'page3: 咯咯',
                upMsg: true,         // 是否向上级窗口发送的消息
            }

            window.opener.postMessage(msg);
        }
    }

    function openBroadcast() {
        window.addEventListener('message', function (e) {
            const { data, upMsg } = e.data;
            alert(data);

            // 收到向上传递的消息,有父级窗口,继续向上传递
            if (window.opener && !window.opener.closed && upMsg) {
                window.opener.postMessage(e.data);
            }

            const openingWins = wins.filter(item => !item.closed); // 过滤已被关闭的页面

            // 收到向下传递的消息,有子级窗口,继续向下传递
            if (openingWins.length && !upMsg) {
                openingWins.forEach(item => item.postMessage(e.data))
            }
        });
    }
    openBroadcast()

    // 关闭广播
    function closeBroadcast() {
        // TODO
    }
</script>


非同源页面通信


iframe 【代理】

父A = 非同源应用A (e.g. http://localhost:63342/demo/iframe/page1.html)
子A = 非同源iframe桥(e.g. http://localhost:8081/)

父B = 非同源应用B (e.g. http://192.168.2.112:3000/)
子B = 非同源iframe桥 (e.g. http://localhost:8081/)

  1. 所有消息都先发给iframe桥
const bridgeIframe = document.getElementById('bridgeIframe');
const msg = {
    type: 'bridge-msg',
    data: 'page1: 咯咯'
};
bridgeIframe.contentWindow.postMessage(msg, '*');
// window.frames[0].window.postMessage(msg, '*');
  1. iframe接收后发起同源广播,同源广播可以在另一个应用中监听
// 接收iframe消息
window.addEventListener('message', (e) => {
  const { data } = e;

  // 转发消息,消息会进入另一个跨域应用的iframe桥的bc.onmessage中
  if (data.type === 'bridge-msg') {
    this.bc.postMessage(`${data.data}`)
  }
});
  1. iframe通过窗口对象转发消息
window.parent.postMessage(e.data, '*');



完整代码

// page1 纯html页面,webstorm服务器运行

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>page1</title>
</head>
<body>
    <span></span>

    <button onclick="sendMessage()">发广播</button>
</body>
</html>

<script>

    // 发消息
    function sendMessage() {
        const bridgeIframe = document.getElementById('bridgeIframe');
        const msg = {
            type: 'bridge-msg',
            data: '咯咯'
        };
        bridgeIframe.contentWindow.postMessage(msg, '*');
    }

    // 创建桥梁
    function createBridge() {
        const bridgeIframe = document.createElement('iframe');
        bridgeIframe.setAttribute('id', 'bridgeIframe');
        // bridgeIframe.style.display = 'none';
        bridgeIframe.src = 'http://localhost:8081/';
        document.body.appendChild(bridgeIframe);
    }
    createBridge();

    // 监听iframe桥发来的消息
    window.addEventListener('message', function(e) {
        const { data, origin } = e;
        document.querySelector('span').innerText = `${origin}: ${data}`;
    });
</script>
// bridgeIframe Vue2应用 http://localhost:8081/

<button @click="sendMessage">发广播</button>

<script>
  export default {
    name: 'App',
    data() {
      return {
        bc: null,
      }
    },
    methods: {
      // 开启广播
      openBroadcast() {
        this.bc = new BroadcastChannel('cctv');
  
        // 接收广播
        // bc广播是同源广播,因为两个跨域应用
        this.bc.onmessage = (e) => {
          console.log(`iframe桥接收到的消息:${e.data}`);
          window.parent.postMessage(e.data, '*');
        };
  
        // 异常处理
        this.bc.onmessageerror = function(e) {
          console.warn('onmessageerror');
        };
      }
    },
    mounted() {
      this.openBroadcast();
  
      // 接收iframe消息
      window.addEventListener('message', (e) => {
        const { data } = e;
  
        // 转发消息,消息会进入另一个跨域应用的iframe桥的bc.onmessage中
        if (data.type === 'bridge-msg') {
          this.bc.postMessage(`${data.data}`)
        }
      });
    }
  }
</script>
// page2 React应用 http://192.168.2.112:3000

import './App.css';
import { useEffect, useState } from "react";

function App() {
    const [msg, setMsg] = useState('');

    useEffect(() => {
        createBridge();

        // 监听iframe桥发来的消息
        window.addEventListener('message', function (e) {
            const { data, origin } = e;
            setMsg(`${origin}: ${data}`);
        });
    }, [])


    // 创建桥梁
    const createBridge = () => {
        const bridgeIframe = document.createElement('iframe');
        bridgeIframe.setAttribute('id', 'bridgeIframe');
        // bridgeIframe.style.display = 'none';
        bridgeIframe.src = 'http://localhost:8081/';
        document.body.appendChild(bridgeIframe);
    }

    // 发消息
    const sendMessage = () => {
        const bridgeIframe = document.getElementById('bridgeIframe');
        const msg = {
            type: 'bridge-msg',
            data: '哒~'
        };
        bridgeIframe.contentWindow.postMessage(msg, '*');
    }

  return (
    <div className="App">
        { msg }

        <button onClick={sendMessage}>发广播</button>
    </div>
  );
}

export default App;


参考文章

面试官:前端跨页面通信,你知道哪些方法?

  • 4
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值