如何实现跨标签页通讯

什么是跨标签页通讯

同一浏览器,可以打开多个标签页,跨标签页通讯就是,一个标签页能够发消息给另一标签页。

有哪些实现方案

  1. localStorage (window.onstorage事件监听)
  2. BroadcastChannel(广播)
  3. ServiceWorker (代理服务线程)
  4. SharedWorker + 轮询
  5. indexedDB + 轮询
  6. cookie + 轮询
  7. window.open + window.postMessage()
  8. WebSocket + 后端服务

方案一:localStorage

基于storage事件

页面一(localStorage1.html):

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>localStorage1</title>
</head>
<body>
    <h1>localStorage1</h1>
    <ul id="ul"></ul>
    <script>
        /** 更新视图*/
        function changeView(){
            const ul = document.getElementById('ul');
            ul.innerHTML = '';
            for(let i = 0; i < localStorage.length; i++){
                const key = localStorage.key(i);
                const value = localStorage.getItem(key);
                const li = document.createElement('li');
                li.textContent = `${key}: ${value}`;
                ul.appendChild(li);
            }
        }
        /** 数据更新-更新视图*/
        function changeData(key,value){
            localStorage.setItem(key,value);
            changeView();
        }
        /** 更新localStorage中数据 */
        changeData('name','张三')
        changeData('age','18')
    </script>
</body>
</html>

页面二(localStorage2.html):

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>localStorage1</title>
</head>
<body>
    <h1>localStorage2</h1>
    <ul id="ul"></ul>
    <script>
        function changeView(){
            const ul = document.getElementById('ul');
            ul.innerHTML = '';
            for(let i = 0; i < localStorage.length; i++){
                const key = localStorage.key(i);
                const value = localStorage.getItem(key);
                const li = document.createElement('li');
                li.textContent = `${key}: ${value}`;
                ul.appendChild(li);
            }
        }
        changeView();
        //当localStorage发生变化时,会触发storage事件
        window.addEventListener('storage',changeView)
    </script>
</body>
</html>

方案二:BroadcastChannel

BroadcastChannel可以创建一个用于广播的通信频道,当所有页面都监听同一频道的消息时,其中某一个页面通过它发送的消息就会被其他页面接收到,前提是同源页面。

页面1(channel1.html):

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>
    <h1>BroadcastChannel1-Tom</h1>
    <p>接收消息:</p>
    <p id="message" style="white-space: pre;"></p>
    <input type="text" name="message" id="messageInput">
    <button onclick="postMessage()">发送消息</button>
    <script>
        const messageElement = document.getElementById('message');
        const messageInput = document.getElementById('messageInput');
        const bc = new BroadcastChannel('channel');
        bc.onmessage = function(e) {
            console.log('收到消息:', e.data);
            messageElement.textContent += '\n'+e.data;
        }
        function postMessage() {
            console.log('发送消息');
            const message = messageInput.value;
            bc.postMessage(message);
            messageInput.value = '';
        }
    </script>
 </body>
 </html>

页面2(channel1.html):

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>
   <h1>BroadcastChannel2-Jerry</h1>
   <p>接收消息:</p>
   <p id="message" style="white-space: pre;"></p>
   <input type="text" name="message" id="messageInput">
   <button onclick="postMessage()">发送消息</button>
   <script>
       const messageElement = document.getElementById('message');
       const messageInput = document.getElementById('messageInput');
       const bc = new BroadcastChannel('channel');
       bc.onmessage = function(e) {
           console.log('收到消息:', e.data);
           messageElement.textContent += '\n'+e.data;
       }
       function postMessage() {
           console.log('发送消息');
           const message = messageInput.value;
           bc.postMessage(message);
           messageInput.value = '';
       }
   </script>
</body>
</html>

方案三:ServiceWorker

Service worker 本质上充当 Web 应用程序、浏览器与网络(可用时)之间的代理服务器。

sw.js文件-代理服务器

javascript 复制代码
// 消息会先到达这里,然后发送到其他客户端
self.addEventListener('message', async (event)=> {
    // 首先获取所有注册了serviceWorker的客户端
    self.clients.matchAll().then((clients)=>{
        // 遍历所有客户端       
        clients.forEach((client)=>{
            // 向每个客户端发送消息
            client.postMessage(event.data);
        })
    })
});

service1.html文件-客户端1

javascript 复制代码
<!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>
    <h1>service1</h1>
    <input type="text" id="content">
    <button id="bt">发送</button>
    <script>
        /**注册serviceWorker*/
        navigator.serviceWorker.register('sw.js').then(function(registration){
            console.log('service worker 注册成功');
        })
        const bt = document.getElementById('bt');
        bt.addEventListener('click',function(){
            const message = document.getElementById('content').value;
            // controller控制器发送消息
            navigator.serviceWorker.controller.postMessage(message);
        })
    </script>
</body>
</html>

service2.html文件-客户端2

javascript 复制代码
<!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>
    <h1>service1</h1>
    <script>
        /**注册同一serviceWorker*/
        navigator.serviceWorker.register('sw.js').then(function(registration){
            console.log('service worker 注册成功');
        })
        //监听onmessage事件
        navigator.serviceWorker.onmessage = function(event){
            console.log('收到消息',event.data);
        }
    </script>
</body>
</html>

方案四:SharedWorker + 轮询

SharedWorkerWorker的一种,它允许你在多个页面之间共享一个Worker

shared.js(worker)

javascript 复制代码
let data = "";//存储用户发送的信息
onconnect = (event) => {
    const port = event.ports[0];//获取客户端端口
    port.onmessage = (event) => {
        if (event.data==='get') {
            port.postMessage(data);//向客户端发送消息
        }else{
            data = event.data;//将用户发送的信息存储到data变量中
        }
    }
}

shared1.html(页面一)

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>
    <h1>shared1</h1>
    <input type="text" id="content">
    <button id="btn">发送</button>
    <script>
        const btn = document.getElementById('btn');
        const content = document.getElementById('content');
        const message = document.getElementById('message')
        // 创建SharedWorker
        const shared = new SharedWorker('shared.js');
        btn.onclick = function(){
            // 向SharedWorker发送消息
            shared.port.postMessage(content.value);
            content.value = '';
        }
    </script>
</body>
</html>

shared2.html(页面二)

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>
    <h1>shared1</h1>
    <input type="text" id="content">
    <button id="btn">发送</button>
    <script>
        const btn = document.getElementById('btn');
        const content = document.getElementById('content');
        const message = document.getElementById('message')
        // 创建SharedWorker
        const shared = new SharedWorker('shared.js');
        btn.onclick = function(){
            // 向SharedWorker发送消息
            shared.port.postMessage(content.value);
            content.value = '';
        }
    </script>
</body>
</html>

方案五:IndexedDB+轮询