实现浏览器内多个标签页面之间通信的四种方法


前言

文章开始之前我先解释一下什么是浏览器标签页面之间通信,通俗点讲就是我在浏览器中开了多个窗口,在其中一个窗口做了一些行为等其它的窗口不用刷新也能有相关表现。比如我在浏览器开一个商品信息窗口,一个购物车窗口,我在商品信息窗口某个商品上点击加入购物车,购物车窗口中就自动多出一个商品,不用去手动刷新,这就是两个标签页面之间的通信,那么怎么实现呢?有四种方式(我目前就知道这么多),接下来我就一一演示并详细解释其原理。

一、cookie + setInterval方式

  • 首先要想在多个窗口中通信,通信的内容一定不能放在window对象中,因为window是当前窗口的作用域,里面的内容只以属于当前窗口。有一种方式就是放在cookie中,cookie是浏览器的本地存储机制,和窗口无关,存在于硬盘上都可以读取。接下来我们就来演示一下。

    新建两个html页面,send.html用来发送消息 和 receive.html用来接收消息。

    <!-- send.html -->
    <body>
    <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>
    </body>
    你可以在浏览器调试的application中看到cookie中存的你在输入框中的信息,也可以打印出来看看cookie存数据的形式——变量名=值
    cookie

    接下来我们就要在receive.html中拿到等号右边的值(也就是Hello)显示到页面上,怎么拿呢?有人会立马想到字符串截取,我们可以先找到等号位置然后slice截取

    <!-- receive.html -->
    <body>
    <h1>收到消息:<span id="recMsg"></span></h1>
    <script>
      var str = document.cookie;
      var i = str.indexOf("=");
      var msg = str.slice(i+1,);
      recMsg.innerHTML = msg;
    </script>
    </body>
    可以在页面显示send发送的内容,但是我不想发送一个,我想发送两个信息或者三个,我们再给send.html加一个input输入框
    <!-- send.html -->
    <body style="text-align: right">
    <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()!==""){
          document.cookie = "msg1=" + msg1.value.trim();
          document.cookie = "msg2=" + msg2.value.trim();
        }
      }
      console.log(document.cookie);
    </script>
    </body>
    输出cookie的内容为msg=Hello; msg1=Hello; msg2=world,只要变量名不一样就被认为是两个cookie,所以之前的msg还存在没有被覆盖。那现在假如我想拿msg1的值,怎么拿?还字符串截取?(除非你想掉头发哈哈,我可心疼我那不怎么乌黑但很亮丽的头发),如果我们能把拿到的字符串转成JSON字符串{"msg":"Hello","msg1":"Hello","msg2":"world"},再用JSON.parse()解析成对象,这样用点访问不就很简单吗?接下来我们就来转换一下。
    <body>
    <!-- receive.html -->
    <h1>收到消息:<span id="recMsg"></span></h1>
    <script>
      // 与原字符串相比,JSON字符串要把等号换成冒号,分号空格换成逗号空格,两边加大括号
      var cookies = '{"'+document.cookie.replace(/=/g,'":"').replace(/;\s+/g,'", "')+'"}';
      cookies = JSON.parse(cookies);
      console.log(cookies);
      recMsg.innerHTML = cookies.msg1;
    </script>
    </body>
    这样想取哪一个值直接用cookies点就行了,注意: JSON字符串中必须用双引号。现在还有一个问题,在send页面发送消息receive页面不能实时更新,需要手动刷新。我们可以用setInterval定时器解决,也把上面的字符串的转换封装成函数可以重用。
    <!-- receive.html -->
    <body>
    <h1>收到消息:<span id="recMsg"></span></h1>
    <script>
      function getValue(key){
        var cookies = '{"'+document.cookie.replace(/=/g,'":"').replace(/;\s+/g,'", "')+'"}';
        cookies = JSON.parse(cookies);
        return cookies[key];
      }
      setInterval(function(){
        recMsg.innerHTML = getValue("msg1");
      },500)
    </script>
    </body>

总结

缺点:

  • cookie空间有限,浏览器在每一个域名下最多能设置30-50个cookie,容量最多为4k左右。
  • 每次HHTP请求才会把当前域的cookie发送到服务器上,包括只在本地才用到的而服务器不用的,浪费带宽。
  • setInterval频率设置过大会影响浏览器的性能,过小会影响时效性。

优点:每个浏览器都兼容

二、localStorage方式

localStorage比cookie好在它在setItem存东西时会自动触发整个浏览器的storage事件,除了当前页面之外,所有打开的标签窗口都会受影响。那我们来看一下用localStorage怎么实现。

<!-- send.html -->
<body style="text-align: right">
  <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>
</body>

可以打开application看localStorage里存的值。

localStorage

<!-- receive.html -->
<body>
  <h1>收到消息:<span id="recMsg"></span></h1>
  <script>
    function load(){
      recMsg.innerHTML = localStorage.getItem("msg1");
    }
    load();
    // 任何页面修改了localStorage的值,都会自动触发其他页面中的storage事件
    // 只要storage一变化我们读取localStorage中对应的值显示到页面上
    window.addEventListener("storage",function(){
      load();
    });
  </script>
</body>

总结

  • 缺点:1、localStorage是h5的属性,高版本的浏览器才支持,而且不同浏览器localStorage大小了限制不统一。2、localStorage只能监听非己页面的数据变化。
  • 优点:解决了cookie容量小和时效性的问题。

三、webSocket方式

前面两种方式只用到了客户端,没有用到服务端,只用到了浏览器就完成了多个窗口的通信。而webSocket需要用到服务端,send.html发送消息到WebSocketServer,WebSocketServer再实时把消息发给receive.html,其实这就是实时通信的原理(微信、qq、淘宝旺旺等)
webSocket

新建webSocket文件夹,在webSocket目录下打开终端,运行npm init初始化一个简单的node项目(因为需要引入ws包),一直按回车到结束就初始了一个简单的node项目。再安装ws包,运行npm i -save ws,在webSocket目录下新建sever.js、send.html、receive.html文件

// 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文件 -->
<body style="text-align: right">
  <input id="msg" type="text"> 
  <button id="send">发送</button>
  <script>
    //建立到服务端webSocket连接
    var ws = new WebSocket("ws://localhost:8080");
    send.onclick = function(){
      if(msg.value.trim()!==""){
        // 将消息发到服务器
        ws.send(msg.value.trim());
      }
    }
  </script>
</body>
<!-- receive.html文件 -->
<body>
  <h1>收到消息:<span id="recMsg"></span></h1>
  <script>
    //建立到服务端webSocket连接
    var ws = new WebSocket("ws://localhost:8080");
    //当连接被打开时,注册接收消息的处理函数
    ws.onopen=function(event) {
      //当有消息发过来时,就将消息放到显示元素上
      ws.onmessage=function(event) {
        recMsg.innerHTML=event.data;
      }
    }
  </script>
</body>

总结

  • 缺点:1、它需要服务端的支持才能完成任务。如果socket数据量比较大的话,会严重消耗服务器的资源。
    2、必须要在服务端项目中写服务端监听程序才能支持。
  • 优点:使用简单(客户端简单,服务端苦逼了),功能灵活、强大,如果部署了WebSocket服务器,可以实现很多实时的功能。

四、SharedWorker方式

  • WebWorker的升级版,WebWorker只能在一个窗口内使用,而现在我们需求是多个窗口之间通信,就要用SharedWorker了。
  • SharedWorker原理和WebWorker几乎是一样的,只不过SharedWorker可以跨多个页面使用。
  • SharedWorker也是纯客户端的,没有服务端的参与。
  • SharedWorker在客户端有一个自己维护的对象worker.js,消息存储在worker.js中的data中
  • SharedWorker不如localStorage的是接收消息不是自动的,也要用定时器实时从worker.js中获取消息。
    SharedWorker

新建SharedWorker文件夹,并在该目录下创建worker.js、send.html和receive.html

// 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文件 -->
<body>
  <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>
</body>
<!-- receive.html文件 -->
<body>
  <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>
</body>

先写到这了,欢迎交流个人博客


文章作者: Love--金哥哥
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Love--金哥哥 !
评论
 上一篇
一文搞懂Js中的面向对象 一文搞懂Js中的面向对象
前言面向对象是Js中永恒不变的话题,学好Js的面向对象编程能很大程度上提高代码的重用率和可维护性。这篇文章打算从基本概念到重点难点通过案例演示带你掌握面向对象编程。 基本概念 对象是程序中描述现实中一个具体事物的属性和功能的程序结构,程序中
2020-03-31
下一篇 
正则表达式中的零宽断言 正则表达式中的零宽断言
前言在使用正则表达式时,有时我们需要捕获的内容前后必须是特定的内容,但又不捕获这些特定的内容,这个时候就要使用零宽断言了。零宽断言和它的名字一样,是一种零宽度的匹配,它匹配的内容不会保存到结果中去,最终匹配结果只是一个位置而已。零宽断言是给
2020-03-29
  目录