likes
comments
collection
share

如何实现浏览器内多个标签页之间的通信?

作者站长头像
站长
· 阅读数 11

1. 通过localStorage实现

localStorageWeb Storage API的一部分,用于在客户端存储数据,JS为它提供了事件监听机制——storage事件,通过监听storage事件变化可以实现跨浏览器标签页通信。

下面是在vue中使用的示例代码:

代码

发送消息页面:

  1. 调用setItem方法,第一个参数是一个标识,第二个参数为传输的具体数据。
//  页面dom内容
<template>
    <div class="send-message">
        <div class="title">我是消息发送页面:</div>
        <div class="content">
            <el-button size="small" type="primary" @click="clickSend">点击按钮,发送消息</el-button>
            <div>发送内容:{{ message }}</div>
        </div>
    </div>
</template>
// js部分
<script>
    export default {
        name: 'send-message',
        data() {
            return {
                message: ''
            }
        },
        methods: {
            clickSend() {
                // 随机数 
                const count = Math.floor(10000 * Math.random());
                this.message = count;
                // 此处标识的名字为sendMessage
                window.localStorage.setItem('sendMessage', count);
            }
        },
    }
</script>

接收消息页面:

  1. 通过监听事件storage得到的参数是一个对象,对象中的key用来对应是否为目标来源对象。
  2. 最新的数据消息数据存储在newValue字段中,oldValue字段为上一次消息通信时的数据。
<template>
    <div class="get-message">
        <div class="title">我是消息接收页面:</div>
        <div class="content">
            <div>接收内容:{{ message }}</div>
        </div>
    </div>
</template>
<script>
export default {
    name: 'get-message',
    data() {
        return {
            message: ''
        }
    },
    mounted() {
        window.addEventListener('storage', this.getMessage);
    },
    destroyed() {
        window.remveEventListener('storage', this.getMessage);
    },
    methods: {
        getMessage(e) {
            console.log(e);
            if (e.key === 'sendMessage') {
                this.message = e.newValue;
            }
        }
    },
}
</script>

下面是getMessage方法接收参数在控制台中打印出来的具体信息:

如何实现浏览器内多个标签页之间的通信?

页面效果:

如何实现浏览器内多个标签页之间的通信?

如何实现浏览器内多个标签页之间的通信?

2. 通过BroadcastChannel API实现

在同一应用中需要在不同标签页(或者iframe)之间共享或同步信息时,BroadcastChannel API 简单且高效。

注意:

  1. BroadcastChannel API 只能在同源(相同的协议、主机和端口)的上下文之间工作。不能使用 BroadcastChannel 来实现跨域通信。

  2. 当标签页关闭时,其对应的 BroadcastChannel 对象会被自动关闭,无需手动销毁

代码

1. 通信

我们沿用上面的dom代码,只修改js部分的代码。

消息发送页面:

  1. mounted钩子中创建一个 BroadcastChannel 对象,通道名称为my-channel;
  2. 使用postMessage方法发送消息;
mounted() {
    this.channel = new BroadcastChannel('my-channel');
},
methods: {
    clickSend() {
        const count = Math.floor(10000 * Math.random());
        this.message = count; // 此处代码是为了在dom页面上展示用的,对通信的代码来说没有意义。
        this.channel.postMessage(count);  
    }
}

消息接收页面:

  1. mounted钩子中创建一个指向同一频道名的 BroadcastChannel 对象,即my-channel;
  2. 通过监听 onmessage 事件以接收消息;
mounted() {
    this.channel = new BroadcastChannel('my-channel');
    this.getMessage();
},
methods: {
    getMessage(e) {
        this.channel.onmessage = e => {
            console.log(e);
            this.message = e.data;
        }
    }
}

2. 回复消息

BroadcastChannel还可以实现回复消息操作,代码如下:

发送消息页面:

  1. 在此页面也定义一个getMessage方法,用来监听onmessage方法。
mounted() {
    this.channel = new BroadcastChannel('my-channel');
    this.getMessage();
},
 methods: {
    getMessage() {
        this.channel.onmessage = e => {
            this.message = e.data;
        }
    },
    clickSend() {
        const count = Math.floor(10000 * Math.random());
        this.message = count; // 此处代码是为了在dom页面上展示用的,对通信没有什么影响。
        this.channel.postMessage(count);  
    }
},

接收消息页面:

mounted() {
    this.channel = new BroadcastChannel('my-channel');
    this.getMessage();
},
methods: {
    getMessage(e) {
        this.channel.onmessage = e => {
            console.log(e);
            this.message = e.data;
            // 通过postMessage可以选择性的回复消息
            this.channel.postMessage(`我收到消息了${e.data}`)
        }
    }
}

页面效果:

发送消息页面:

如何实现浏览器内多个标签页之间的通信?

接收消息页面:

如何实现浏览器内多个标签页之间的通信?

3. 手动关闭 BroadcastChannel

当某些场景下,我们想手动关闭BroadcastChannel通信,可以调用它的 close 方法。

clickCloseSend() {
    this.channel.close();
}

3. 通过cookie实现

在js中,由于直接监听cookie的变化是不可能的,因为cookie是存储在浏览器中的,并且JavaScript没有提供直接监听cookie变化的事件或API,可以通过轮询或者其它方式来检测cookie的变化。

下面这个示例是使用最常见的方法定期轮询cookie的值。通过设置一个定时器,每间隔两秒去查询一次。

注意:

  1. 这种方法应该谨慎使用,并仅在必要时使用。
  2. 此方法并不是实时的,并且可能会因为定时器的精度而引入一些延迟。
  3. 频繁地检查cookie可能会对性能产生一定的影响。

代码

发送消息页面:

clickSend() {
    const count = Math.floor(10000 * Math.random());
    // 此处代码是为了在dom页面上展示用的,对通信没有什么影响。
    // this.message = count;
    // 设置cookie
    document.cookie = `sendData=${count}`;
},

接收消息页面:

  1. 再页面初始化时,定义一个定时器,每隔两秒钟轮询一次。
  2. 获取cookie,判断cookie中是否存在sendData=这个标识,若存在就将此段数据截取出来。
mounted() {
    // 定时器
    setInterval(this.getMessage, 2000);
},
methods: {
    getMessage() {
        const cookies = document.cookie;
        const cookieArr = cookies.split(';');
        let sendData = null;
        for(let i = 0; i < cookieArr.length; i++) {
            const cookie = cookieArr[i].trim();
            if (cookie.startsWith('sendData=')) {
                sendData = cookie.substring('sendData='.length, cookie.length);
                break;
            }
        }
        this.message = sendData;
    }
}

这个示例的页面效果跟上面两种示例的效果一样,这里就不贴图了。

4. 通过window.postMessage实现。

window.postMessage 是Web API中的一个方法,它允许来自不同源的文档安全地相互通信,通过调用 postMessage() 方法并指定目标窗口的origin,可以将消息发送到其他标签页,并通过监听message事件来接收消息。

代码

发送消息页面:

clickSend() {
    const count = Math.floor(10000 * Math.random());
    if (!this.targetWindow) {
        this.targetWindow = window.open('http://example.cn/get-message', '_blank');
    } else {
        this.targetWindow.postMessage(count, 'http://example.cn');
        // 或者通过在传输的数据中用一个key来标识,目标源可以设置为*, 下面的key字段用做标识,在数据接收时通过判断这个key的值来确定是否接受此数据。
        // this.targetWindow.postMessage({key: 'send-data', data: count}, '*');
    }
},

接收消息页面:

mounted() {
    window.addEventListener('message', this.getMessage, false);
},
destroyed() {
    window.remveEventListener('message', this.getMessage, false)
},
methods: {
    getMessage(e) {
        // 通过传输数据中的key来判断是否要接受数据。
        // if (e.data.key === 'send-data') {
        //    this.message = e.data.data;
        // }
        this.message = e.data.data;
    },
},

注意:

  1. 当使用 postMessage 时,请确保正确地验证消息的来源(event.origin),以防止跨站脚本攻击(XSS)。
  2. 发送和接收消息的双方必须遵守同源策略(Same-Origin Policy),除非消息被明确允许跨源发送。
  3. 如果你希望发送复杂的数据结构(如对象或数组),postMessage 可以自动处理这些结构的序列化与反序列化。

5. 通过indexedDB实现

IndexedDB是浏览器提供的一个客户端数据库,是一个事务型的数据库系统,能够存储大量结构化数据(包括文件/ blob),可以在不同的标签页之间存储和读取数据。其实它主要用于在用户浏览器中持久化存储数据(不同标签页中共享数据),而不是用于不同标签页或窗口之间的实时通信

一个可行的方案是:一个标签页可以将数据写入IndexedDB,其他标签页可以监听IndexedDB的变化事件或定时从IndexedDB中读取数据来实现数据的共享和状态的同步。

由于我没有试过这种方式(其实是自己懒),就不贴代码了,有兴趣的掘友可以自己研究一下。另外在使用indexedDB的时候需要注意以下几点:

  1. 不同的浏览器可能对IndexedDB的支持程度和支持的版本有所不同,为了确保应用的稳定性和可用性,建议自己查查不同浏览器版本对IndexedDB的支持情况。
  2. IndexedDB的所有操作都是异步的,这意味着它们不会立即完成,而是稍后在事件循环中完成,所以需要正确管理回调函数、Promise或async/await,确保自己的代码按预期执行。
  3. IndexedDB操作可能会失败,例如由于数据库版本冲突、存储空间不足或其他原因。因此,始终要准备好捕获和处理错误。可以用try...catch来处理。

这篇文章就到这里了,有描述不正确的地方欢迎掘友们纠错~。

参考文章

  1. developer.mozilla.org/en-US/docs/…
  2. blog.51cto.com/u_15315508/…
转载自:https://juejin.cn/post/7347009547741560886
评论
请登录