1. 引子
很多时候,浏览器的不同标签页都属于同一个网站,而且这些标签页会使用一些相同的数据,这时我们就要让这些数据保持同步。
比如在商品列表页将一个物品添加至购物车,那么购物车页中就应该自动多出一个商品,这就是两个页面之间的通信。
实现不同标签页之间的通信一共有4种方式。
2. 四种方式
2.1 cookie + setInterval
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中获取数据