计网基础-浏览器标签页间通信方式

loading 2022年11月25日 52次浏览

1. 引子

很多时候,浏览器的不同标签页都属于同一个网站,而且这些标签页会使用一些相同的数据,这时我们就要让这些数据保持同步。

比如在商品列表页将一个物品添加至购物车,那么购物车页中就应该自动多出一个商品,这就是两个页面之间的通信。

实现不同标签页之间的通信一共有4种方式。

2. 四种方式

2.1.1 原理和实现

要想在多个窗口间通信,通信内容一定不能放在window对象中,因为window只能代表当前窗口的作用域,里面的内容也直属于当前的窗口。

第一种方式是放在cookie中,cookie是浏览器本地的存储机制,和窗口无关。

首先创建一个send.html页面,用来发送消息

    <input id="msg" type="text">
    <button id="send">发送</button>
    <script>
        send.onclick = function () {
            // 输入框里有值才会往cookie里存
            if (msg.value.trim() !== "") {
                // cookie里面存的是‘变量名=值’的形式
                document.cookie = "msg=" + msg.value.trim();
            }
        }
        console.log(document.cookie);
    </script>

然后在vscode中启动live server,并在输入框内输入cookie信息,会发现信息成功被录入到cookie中:

然后再创建一个receive.html页面,尝试拿到cookie中的值。首先将拿到的字符串转换为JSON字符串,再通过JSON.parse()转换成对象,最后直接访问对象的属性即可。

        // 与原字符串相比,JSON字符串要把等号换成冒号,分号空格换成逗号空格,两边加大括号
        var cookies = '{"' + document.cookie.replace(/=/g, '":"').replace(/;\s+/g, '", "') + '"}';
        //将JSON字符串解析成对象
        cookies = JSON.parse(cookies);
        //通过.操作符访问对象中的属性并弹出即可
        alert(cookies.msg)

最后再解决一个问题,send发送消息后receive不能实时更新,需要手动刷新。可以通过添加一个setInterval定时器解决,每隔一段时间就自动读取一次cookie。

        function getValue(objKey) {
            var cookies = '{"' + document.cookie.replace(/=/g, '":"').replace(/;\s+/g, '", "') + '"}';
            cookies = JSON.parse(cookies);
            return cookies[objKey];
        }
        setInterval(()=>{
            alert(getValue('msg'));
        } , 500)

2.1.2 总结

优点:

  • 每个浏览器都兼容

缺点:

  • cookie空间较小,每个域名下cookie的数量(30-50个)和容量(4k左右)都收到限制
  • 每次HTTP请求都会把当前域的cookie发送到服务器上,包括服务器用不上的,浪费带宽
  • setInterval频率过高影响浏览器的性能,而频率过低又会影响时效性。

2.2 localStroage

2.2.1 原理和实现

localStroage相比cookie的好处在于当它通过setItem存东西时会自动触发整个浏览器的stroage事件,也就是说除了当前页面,所有打开的标签窗口都会受影响。

先来看send页面,将数据存入到localStroage中

    <input id="msg1" type="text">
    <input id="msg2" type="text">
    <button id="send">发送</button>
    <script>
        send.onclick = function () {
            if (msg1.value.trim() !== "" && msg2.value.trim() !== "") {
                localStorage.setItem("msg1", msg1.value.trim());
                localStorage.setItem("msg2", msg2.value.trim());
            }
        }
    </script>

随后再在另一个页面中接收localStroage的信息:

    <h3>第一条数据:<span id="recMsg1"></span></h3>
    <h3>第二条数据:<span id="recMsg2"></span></h3>
    <script>
        function load() {
            recMsg1.innerHTML = localStorage.getItem("msg1");
            recMsg2.innerHTML = localStorage.getItem("msg2");
        }
        load();
        // 任何页面修改了localStorage的值,都会自动触发其他页面中的storage事件
        // 只要storage一变化我们读取localStorage中对应的值显示到页面上
        window.addEventListener("storage", function () {
            load();
        });
    </script>

2.2.2 总结

优点:

  • 解决了cookie容量小和时效性的问题

缺点:

  • localStroage有部分低版本浏览器不支持,而且不同浏览器的localStroage大小限制不统一
  • localStroage监听不到自己页面的变化

2.3 webSocket

2.3.1 原理和实现

前两种方式只用到了客户端(浏览器),没用到服务端

webSocket需要用到服务端,send.html发送消息到WebSocketServer,WebSocketServer再实时把消息发给receive.html,其实这就是实时通信的原理(微信、qq、淘宝旺旺等)

首先通过npm init初始化一个node项目,然后用npm下载好ws包,并把需要的文件建立好。

server.js

//获得WebSocketServer类型
var WebSocketServer = require('ws').Server;
//创建WebSocketServer对象实例,监听指定端口
var wss = new WebSocketServer({ port: 8080 });
//创建保存所有已连接到服务器的客户端对象的数组
var clients = [];

//为服务器添加connection事件监听,当有客户端连接到服务端时,立刻将客户端对象保存进数组中。
wss.on('connection', function (client) {
    console.log("一个客户端连接到服务器");
    // 如果没有这个client对象,说明是第一次连接,就加入到clients中
    if (clients.indexOf(client) === -1) {
        clients.push(client);
        console.log("有" + clients.length + "个客户端在线");
        //为每个client对象绑定message事件,当某个客户端发来消息时,自动触发
        client.on('message', function (msg) {
            console.log("收到消息:" + msg);
            //遍历clients数组中每个其他客户端对象,并发送消息给其他客户端
            for (var c of clients) {
                if (c != client) {
                    c.send(msg);
                }
            }
        })
    }
});

send.html

    <input id="msg" type="text">
    <button id="send">send</button>
    <script>
        //建立到服务端的WebSocket连接
        let ws = new WebSocket('ws://localhost:8080');
        send.onclick = function(){
            if(msg.value.trim() !== ''){
                console.log(msg.value)
                ws.send(msg.value.trim());
            }
        }
    </script>

receive.html

    <h3>收到消息:<span id="recMsg"></span></h3>
    <script>
        //建立到服务端webSocket连接
        var ws = new WebSocket("ws://localhost:8080");
        //当连接被打开时,注册接收消息的处理函数
        ws.onopen = function (event) {
            //当有消息发过来时,就将消息放到显示元素上
            ws.onmessage = function (event) {
                console.log(event)
                recMsg.innerHTML = event.data;
            }
        }
    </script>

最终效果:

但是网页中receive到的data是一个blob对象而不是yoimiya字符串,暂时不知道什么原因。

2.3.2 总结

优点:

  • 客户端使用简单(因为主要靠服务端),功能灵活且强大,可以实现很多实时的功能

缺点:

  • 必须要服务端的支持才能完成任务,如果数据量比较大,会严重消耗服务器的资源
  • 必须在服务端中写服务端监听程序才能支持

2.4 SharedWorker

2.4.1 原理和实现

webWorker的作用是为js提供多线程环境,而sharedWorker是webWorker中的一种,webWorker只能在一个窗口内使用,而我们现在需求多窗口通信,就要用到sharedWorker了。

创建一个文件夹后在其中加入下列三个文件
worker.js 配置文件

//在所有SharedWorker共享的worker.js中,保存一个data变量,用于存储多个worker共享的数据
let data = "连接成功";
//必须提供一个名为onconnect的事件处理函数
//每当一个页面中new SharedWorker("worker.js")时,就会为新创建的worker绑定onconnect事件处理函数
onconnect = function (e) {
    //获得当前连接上来的客户端对象
    var client = e.ports[0];
    client.postMessage(data);
    //当当前对象收到消息时
    client.onmessage = function (e) {
        //如果消息内容为空,说明该客户端想获取共享的数据data
        if (e.data === "") {
            //就给当前客户端发送data数据
            client.postMessage(data);
        } else {//否则如果消息内容不为空,说明该客户端想要提供新的消息保存在共享的data中,供别人获取
            data = e.data;
        }
    }
}

send.html

    <input type="text" id="msg"><button id="send">发送</button>
    <script>
        var worker = new SharedWorker("worker.js");
        worker.port.start();
        send.onclick = function () {
            if (msg.value.trim() !== "") {
                worker.port.postMessage(msg.value.trim())
            }
        }
    </script>

receive.html

    <h1>收到消息:<span id="recMsg"></span></h1>
    <script>
        var worker = new SharedWorker("worker.js");
        //3. 当worker.js中给当前客户端返回了data,会触发当前客户端的message事件。data的值,自动保存进事件对象e的data属性中
        worker.port.addEventListener("message", function (e) {
            recMsg.innerHTML = e.data;
        })
        worker.port.start();
        //1. 接收端反复向共享的worker.js对象中发送空消息,意为想获取data的值
        setInterval(function () {
            worker.port.postMessage("");
            //2. 只要发送消息,就触发worker.js中的onmessage,onmessage判断是空消息内容,说明客户端想获得data。于是就用postMessage()方法,将data返回给当前客户端
        }, 500);
    </script>

2.4.2 总结

特点:

  • 纯客户端,没有服务端参与
  • 在客户端有一个自己维护的对象worker.js,消息存储在worker.js的data中
  • 原理和webWorker基本相同,只不过可以跨页面使用
  • 接收消息不自动,还是需要定时器来从worker.js中获取数据