面试官:你是如何实现浏览器多标签页之间通信的?

简介: 前言我们都知道浏览器是可以打开很多标签页的,如果每个标签页代表的是单独的一个网站,那么这些标签页之间肯定是不能通信的,如果能通信那估计我们都得凉凉。但是在很多情况下,浏览器中的很多标签页都属于某一个网站,而且这些标签页之间会使用一些相同的数据,这个时候我们就需要让这些标签页的数据都保持同步。比如很多博客网站,点击文章列表通常是打开一个新的标签页进入文章详情页,那么如果我们在文章详情页点赞、评论等操作,而文章列表页也使用了这些数据,这个时候我们需要保持两边的数据一致,衍生出来就是详情页改了数据,需要让列表页知道。总结来看:在某些情况下,实现多标签页之间通信是必要的!

1.localStorage实现通信


借助localStorage实现标签页之间通信在实际项目中使用的很多,因为它操作简单,易于理解。如果你还不是早localStorage的用法,那你一定得恶补了。


localStorage的特点:

  • 同域共享存储空间
  • 持久化将数据存储来浏览器
  • 提供事件监听localStorage变化


这里我们需要重点关注同域共享,如果多个标签页跨域了,那么数据将无法共享。


代码演示:

我们新建两个页面pageA和pageB,利用localStorage实现两个页面之间的通信。

示例代码:

pageA

// pageA.html
<body>
  <h1>pageA</h1>
</body>
<script>
  window.addEventListener("storage", (e) => {
    console.info("localStorage发生变化:", e)
  })
</script>

pageB

// pageB.html
<body>
  <h1>pageB</h1>
  <button id="btnB">添加数据到localStorage</button>
</body>
<script>
  let btnB = document.getElementById("btnB");
  let num = 0;
  btnB.addEventListener("click", () => {
    localStorage.setItem("num", num++)
  })
</script>


当我们点击pageB中的按钮时,会更改localStorage中的值。然后在pageA中的storage监听函数便会监听到localStorage发生变化。


pageA输出结果:74.png


可以看到在pageA中不仅可以拿到改变后的值,还可以拿到改变之前的值。通过这种方式,我们就可以将两个页面的数据进行同步了。


注意点:

  • pageA和pageB同源,即域名、端口、协议等都是相同的。
  • 使用storage事件监听localStorage变化


当然,如果你只是需要两个页面之间数据共享,那么可以不使用storage监听方法,直接通过localStorage.getItem()获取即可。


2.使用websocket


websocket是一种网络通讯协议。我们都知道在使用HTTP协议的时候,我们与服务端都是通过发请求的方式进行通讯的,而且这种通讯只能由客户端发起。websocket协议就弥补了这一缺点,它是一个全双工通信的协议,意味着客户端和服务端可以互相通信,享受平等关系。


最简单列子就是聊天室,我们在聊天室里面可以收消息,也可以发消息,只要我们与服务端通过websocket建立好了连接。


websocket特点:

  • 保持连接状态,HTTP协议是无状态连接,即请求完毕后就会关闭连接。
  • 全双工通信,客户端和服务端平等对待,可以互相通信。
  • 建立在TCP协议之上
  • 没有同源共享策略,即可实现跨域共享


通过以上websocket的特点,我们再来思考如何利用websocket实现多标签页通信?


其实实现原理页比较简单,假如我们pageA和pageB都与服务器建立了websocket连接,那么连个页面都可以实时接收服务端发来的消息,也可以实时向服务端发送消息。如果pageA更改了数据,那么向服务端发送一条消息或数据,服务端在将这条消息或数据发送给pageB即可,这样就简单实现了两个标签页之间的通信。


原理有点类似于”中介“,我们可以通过中介来进行沟通。


代码演示:

我们先来搭建一个简单的websocket服务器,用于pageA和pageB的连接,新建index.js文件。


初始化命令:

npm init -y
npm install --save ws
运行命令:node index.js


代码如下:

index.js

// index.js
let WebSocketServer = require("ws").Server;
let wss = new WebSocketServer({ port: 3000 });
// 创建保存所有已连接到服务器的客户端对象的数组
let clients = [];
// 为服务器添加connection事件监听,当有客户端连接到服务端时,立刻将客户端对象保存进数组中。
wss.on("connection", function (client) {
  console.log("一个客户端连接到服务器");
  if (clients.indexOf(client) === -1) {
    clients.push(client);
    // 接收客户端发送的消息
    client.on("message", function (msg) {
      console.log("收到消息:" + msg);
      // 将消息发送给非自己的客户端
      for (let key of clients) {
        if (key != client) {
          key.send(msg.toString());
        }
      }
    });
  }
});

pageA

// pageA
<script>
  // 创建一个websocket连接
  var ws = new WebSocket('ws://localhost:3000/');
  // WebSocket连接成功回调
  ws.onopen = function () {
    console.log("websocket连接成功")
  }
  // 这里接受服务器端发过来的消息
  ws.onmessage = function (e) {
    console.log("服务端发送的消息", e.data)
  }
</script>

pageB

<script>
  let btnB = document.getElementById("btnB");
  let num = 0;
  btnB.addEventListener("click", () => {
    ws.send(`客户端B发送的消息:${num++}`);
  })
  // 创建一个websocket连接
  var ws = new WebSocket('ws://localhost:3000/');
  // WebSocket连接成功回调
  ws.onopen = function () {
    console.log("websocket连接成功")
  }
</script>


当我们点击pageB中的按钮时,会通过websocket向服务端发送一条消息,服务端接收到这条消息之后,会将消息转发给pageA,这样pageA就得到了pageB传来的数据。


pageA输出结果:75.png

总体来说,原理很简单,只是需要了解websocket。通常情况下,我们不建议使用websocket来进行多标签页通信,因为这回增加服务器的负担。


3.SharedWorker


我们都知道JavaScript是单线程的,单线程有好处也有坏处。为了弥补JS单线程的坏处,webWorker随之被提出,它可以为JS创造多线程环境。如果还不了解webWorker的可以去官网初步了解一下。


sharedWorker就是webWorker中的一种,它可以由所有同源页面共享,利用这个特性,我们就可以使用它来进行多标签页之前的通信。


sharedWorker特点:

  • 跨域不共享,即多个标签页不能跨域
  • 使用port发送和接收消息
  • 如果url相同,且是同一个js,那么只会创建一个sharedWorker,多个页面共享这个sharedWorker


其实它和我们的webSocket实现多页面通讯的原理很类似,都是发送数据和接收数据这样的步骤,shardWorker就好比我们的webSocket服务器。


代码演示:

新建一个worker.js,编写代码。


代码如下:

// worker.js
const set = new Set()
onconnect = event => {
  const port = event.ports[0]
  set.add(port)
  // 接收信息
  port.onmessage = e => {
    // 广播信息
    set.forEach(p => {
      p.postMessage(e.data)
    })
  }
  // 发送信息
  port.postMessage("worker广播信息")
}

pageA

<script>
  const worker = new SharedWorker('./worker.js')
  worker.port.onmessage = e => {
    console.info("pageA收到消息", e.data)
  }
</script>

pageB

<script>
  const worker = new SharedWorker('./worker.js')
  let btnB = document.getElementById("btnB");
  let num = 0;
  btnB.addEventListener("click", () => {
    worker.port.postMessage(`客户端B发送的消息:${num++}`)
  })
</script>

上面的代码就是一个最简单的sharedWorker的应用,我们在pageA页面中初始化了sharedWorker,并且设置了接收消息的监听函数,当sharedWorker初始化完成之后,pageA便会接收到一条消息,如下图:


76.png

后我们在pageB中同样初始化了sharedWorker的示例,点击按钮广播消息,此时pageA便可以收到消息,是不是和websocket的原理很像啊。


pageA输出结果:

77.png


调试sharedWorker:

我们如何查看当前是运行的哪个sharedWorker呢?可以在浏览时输入:chrome://inspect。

找到sharedWorker选项,就可以看到运行的sharedWorker,如下图:78.png

兼容性查看:79.png


总结:

sharedWorker的原理和websocket有点类似,都是广播和接收的原理,但是它也有一些缺点,比如调试不太方便、兼容性不太好。所以使用的时候一定要结合实际情况使用。


4.使用cookie + setInterval


我们都知道cookie可以用来存储数据,而且它是同源共享的,借助它的这些特点,我们就可以利用cookie实现多页面的通讯。


cookie特点:

  • 跨域不共享
  • 具有存储空间限制
  • 请求会自动携带cookie

示例代码:

pageA

<script>
  setInterval(() => {
    //加入定时器,让函数每一秒就调用一次,实现页面刷新
    console.log("cookie",document.cookie)
  }, 1000);
</script>

pageB

<script>
  let btnB = document.getElementById("btnB");
  let num = 0;
  btnB.addEventListener("click", () => {
    document.cookie = `客户端B发送的消息:${num++}`
  })
</script>

输出结果:80.png


这种方式实现的原理非常简单,就是在需要接收消息的页面不断轮询去查询cookie,然后发送消息的页面将数据存储在cookie中,这样就实现了简单的数据共享。


总结


这里介绍了4中实现浏览器多标签页之前通讯的方法,它们优缺点也有优点,有的操作简单,有的已于理解等等,需要根据实际场景选择不一样的方法。

81.png

想要视频学习,可以移步B站:小猪课堂

相关文章
|
5月前
|
Web App开发 编解码 前端开发
面试题22:如何测试Web浏览器的兼容性?
面试题22:如何测试Web浏览器的兼容性?
117 3
|
5月前
|
存储 算法 前端开发
【面试题】说一下浏览器垃圾回收机制?
【面试题】说一下浏览器垃圾回收机制?
|
5月前
|
存储
如何实现浏览器内多个标签页之间的通信
如何实现浏览器内多个标签页之间的通信
67 0
|
2月前
|
存储 JavaScript 容器
【Vue面试题十一】、Vue组件之间的通信方式都有哪些?
这篇文章介绍了Vue中组件间通信的8种方式,包括`props`传递、`$emit`事件触发、`ref`、`EventBus`、`$parent`或`$root`、`attrs`与`listeners`、`provide`与`inject`以及`Vuex`,以解决不同关系组件间的数据共享问题。
|
2月前
|
存储 JavaScript 前端开发
|
2月前
|
存储 安全 Java
【多线程面试题 七】、 说一说Java多线程之间的通信方式
Java多线程之间的通信方式主要有:使用Object类的wait()、notify()、notifyAll()方法进行线程间协调;使用Lock接口的Condition的await()、signal()、signalAll()方法实现更灵活的线程间协作;以及使用BlockingQueue作为线程安全的队列来实现生产者和消费者模型的线程通信。
|
3月前
|
消息中间件 分布式计算 网络协议
从管道路由到共享内存:进程间通信的演变(内附通信方式经典面试题及详解)
进程间通信(Inter-Process Communication, IPC)是计算机科学中的一个重要概念,指的是运行在同一系统或不同系统上的多个进程之间互相发送和接收信息的能力。IPC机制允许进程间共享数据、协调执行流程,是实现分布式系统、多任务操作系统和并发编程的基础。
从管道路由到共享内存:进程间通信的演变(内附通信方式经典面试题及详解)
|
3月前
|
Java 数据格式
Java面试题:简述Java Socket编程的基本流程,包括客户端和服务器的创建与通信。
Java面试题:简述Java Socket编程的基本流程,包括客户端和服务器的创建与通信。
42 0
|
5月前
|
缓存 网络协议 前端开发
面试题:浏览器中输入URL返回页面过程?
面试题:浏览器中输入URL返回页面过程?
102 0
|
5月前
|
消息中间件 前端开发 Java
【面试题】前端必修-浏览器的渲染原理
【面试题】前端必修-浏览器的渲染原理
下一篇
无影云桌面