跨标签页通信
顾名思义,就是一个标签页发送消息给另一个标签页。
实现跨标签页通信的方式有以下几种:
- BroadcastChannel
- Service Worker
- LocalStorage window.onstorage监听
- Shared Worker 定时器轮询(setInterval)
- IndexDB 定时器轮询(setInterval)
- cookie 定时器轮询(setInterval)
- window.open window.postMessage
- Websocket
- Web Worker
BroadcastChannel
BroadcastChannel可以帮我们创建一个用于广播的通信频道。当所有页面都监听同一频道的信息时,其中某一个页面通过它发送的消息就会被其他所有页面监听到,但前提是同源页面。
html
<!-- index1.html -->
<html>
<head>
<title>页面1</title>
</head>
<body>
<input type="text" id="ipt">
<button id="btn">发送数据</button>
<script>
const ipt = document.querySelector("#ipt");
const btn = document.querySelector("#btn");
// 创建一个名字叫load1的BroadcastChannel实例
const bc = new BroadcastChannel('load1');
btn.onclick = function () {
bc.postMessage({
value: ipt.value
});
}
</script>
</body>
</html>html
<!-- index2.html -->
<html>
<head>
<title>页面2</title>
</head>
<body>
<script>
// 创建一个名字叫load1的BroadcastChannel实例
const bc = new BroadcastChannel('load1');
bc.onmessage = function (e) {
console.log(e.data);
}
</script>
</body>
</html>Service Worker
Service Worker实际上是浏览器和服务器之间的代理服务器,它最大的特点是在页面中注册并安装成功后,运行于浏览器后台,不受页面刷新的影响,可以监听和拦截作用域范围内的所有页面的HTTP请求。
Service Worker的目的在于离线缓存,转发请求和网络代理。
在已经支持Service Worker的浏览器的版本中,很多特性没有默认开启。如果你发现示例代码在当前版本的浏览器中怎么样都无法正常运行,你可能需要开启一下浏览器的相关配置:
- Firefox Nightly: 访问 about:config 并设置 dom.serviceWorkers.enabled 的值为 true; 重启浏览器;
- Chrome Canary: 访问 chrome://flags 并开启 experimental-web-platform-features; 重启浏览器 (注意:有些特性在Chrome中没有默认开放支持);
- Opera: 访问 opera://flags 并开启 ServiceWorker 的支持; 重启浏览器。
html
<!-- index1.html -->
<html>
<head>
<title>页面1</title>
</head>
<body>
<input type="text" id="ipt">
<button id="btn">发送数据</button>
<script>
const ipt = document.querySelector("#ipt");
const btn = document.querySelector("#btn");
window.navigator.serviceWorker.register('sw.js').then(() => {
console.log('service worker注册成功');
})
btn.onclick = function () {
window.navigator.serviceWorker.controller.postMessage({
value: ipt.value
});
}
</script>
</body>
</html>js
// sw.js
// 消息会到达这里
self.addEventListener("message", async event => {
// 首先获取所有注册了service worker的客户端
const clients = await self.clients.matchAll();
// 然后遍历所有客户端,将消息转发出去
clients.forEach(client => {
client.postMessage(event.data);
})
})html
<!-- index2.html -->
<html>
<head>
<title>页面2</title>
</head>
<body>
<script>
window.navigator.serviceWorker.register('sw.js').then(() => {
console.log('service worker注册成功');
})
window.navigator.serviceWorker.onmessage = function (res) {
console.log(res);
}
</script>
</body>
</html>LocalStorage window.onstorage监听
在Web Storage中,每次将一个值存储到本地时,就会触发一个storage事件,该事件只在同一个域下的任何窗口或标签上触发,并且只在已存在的键值发送改变时触发。
由事件监听器发送给回调函数的事件对象有几个属性如下:
- key:被修改的键
- newValue:被修改后的新值
- oldValue:修改前的值
- storageArea:指向事件监听的storage对象
- url:触发storage事件的地址
js
// 页面1
localStorage.setItem('name', 'tom')js
// 页面2
window.onstorage = function (e) {
console.log(e.key);
console.log(e.newValue);
console.log(e.oldValue);
console.log(e.storageArea);
console.log(e.url);
}SharedWorker 定时器轮询(setInterval)
SharedWorker是一种特定类型的worker,它是Web Worker的一种,可以连接多个同源的页面。
html
<!-- index1.html -->
<html>
<head>
<title>页面1</title>
</head>
<body>
<input type="text" id="ipt">
<button id="btn">发送数据</button>
<script>
const ipt = document.querySelector("#ipt");
const btn = document.querySelector("#btn");
const worker = new SharedWorker('worker.js');
btn.onclick = function () {
console.log(ipt.value)
worker.port.postMessage(ipt.value);
}
</script>
</body>
</html>js
// worker.js
let result; // 用来存储用户发送过来的信息
onconnect = function (e) {
let port = e.ports[0];
port.onmessage = function (events) {
if (events.data === 'get') {
port.postMessage(result);
} else {
result = events.data;
}
}
}html
<!-- index2.html -->
<html>
<head>
<title>页面2</title>
</head>
<body>
<script>
const worker = new SharedWorker('worker.js');
worker.port.start();
worker.port.onmessage = function (e) {
if(e.data) {
console.log('来自worker的数据', e.data);
}
}
setInterval(() =>{
worker.port.postMessage('get');
}, 1000)
</script>
</body>
</html>IndexDB 定时器轮询(setInterval)
js
// db.js
/**
* 打开数据库
* @param {object} dbName 数据库的名字
* @param {string} storeName 仓库名称
* @param {string} version 数据库的版本
* @return {object} 该函数会返回一个数据库实例
*/
function openDB(dbName, version = 1) {
return new Promise((resolve, reject) => {
var db; // 存储创建的数据库
// 打开数据库,若没有则会创建
const request = indexedDB.open(dbName, version);
// 数据库打开成功回调
request.onsuccess = function (event) {
db = event.target.result; // 存储数据库对象
console.log("数据库打开成功");
resolve(db);
};
// 数据库打开失败的回调
request.onerror = function (event) {
console.log("数据库打开报错");
};
// 数据库有更新时候的回调
request.onupgradeneeded = function (event) {
// 数据库创建或升级的时候会触发
console.log("onupgradeneeded");
db = event.target.result; // 存储数据库对象
var objectStore;
// 创建存储库
objectStore = db.createObjectStore("stu", {
keyPath: "stuId", // 这是主键
autoIncrement: true // 实现自增
});
// 创建索引,在后面查询数据的时候可以根据索引查
objectStore.createIndex("stuId", "stuId", { unique: true });
objectStore.createIndex("stuName", "stuName", { unique: false });
objectStore.createIndex("stuAge", "stuAge", { unique: false });
};
});
}
/**
* 新增数据
* @param {object} db 数据库实例
* @param {string} storeName 仓库名称
* @param {string} data 数据
*/
function addData(db, storeName, data) {
var request = db
// 事务对象 指定表格名称和操作模式("只读"或"读写")
.transaction([storeName], "readwrite")
.objectStore(storeName) // 仓库对象
.add(data);
request.onsuccess = function (event) {
console.log("数据写入成功");
};
request.onerror = function (event) {
console.log("数据写入失败");
};
}
/**
* 通过主键读取数据
* @param {object} db 数据库实例
* @param {string} storeName 仓库名称
* @param {string} key 主键值
*/
function getDataByKey(db, storeName, key) {
return new Promise((resolve, reject) => {
var transaction = db.transaction([storeName]); // 事务
var objectStore = transaction.objectStore(storeName); // 仓库对象
var request = objectStore.getAll(); // 通过主键获取数据
request.onerror = function (event) {
console.log("事务失败");
};
request.onsuccess = function (event) {
// console.log("主键查询结果: ", request.result);
resolve(request.result);
};
});
}html
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>页面一</title>
</head>
<body>
<h1>新增学生</h1>
<div>
<span>学生学号:</span>
<input type="text" name="stuId" id="stuId">
</div>
<div>
<span>学生姓名:</span>
<input type="text" name="stuName" id="stuName">
</div>
<div>
<span>学生年龄:</span>
<input type="text" name="stuAge" id="stuAge">
</div>
<button id="addBtn">新增学生</button>
<script src="./db.js"></script>
<script>
let btn = document.querySelector("#addBtn");
let stuId = document.querySelector("#stuId");
let stuName = document.querySelector("#stuName");
let stuAge = document.querySelector("#stuAge");
openDB("stuDB", 1)
.then((db)=>{
btn.onclick = function(){
addData(db,"stu",{
"stuId" : stuId.value,
"stuName" : stuName.value,
"stuAge" : stuAge.value,
});
stuId.value = stuName.value = stuAge.value = "";
}
})
</script>
</body>
</html>html
<!-- index2.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>页面二</title>
<style>
table{
border: 1px solid;
border-collapse: collapse;
}
table td{
border: 1px solid;
}
</style>
</head>
<body>
<h1>学生表</h1>
<table id="tab">
<!-- <tr>
<td>学号</td>
<td>姓名</td>
<td>年龄</td>
</tr>
<tr>
<td>1</td>
<td>謝傑</td>
<td>18</td>
</tr>
<tr>
<td>2</td>
<td>张三</td>
<td>19</td>
</tr> -->
</table>
<script src="./db.js"></script>
<script>
function render(arr){
let tab = document.querySelector("#tab");
tab.innerHTML = `
<tr>
<td>学号</td>
<td>姓名</td>
<td>年龄</td>
</tr>
`;
let str = arr.map(item=>{
return `
<tr>
<td>${item.stuId}</td>
<td>${item.stuName}</td>
<td>${item.stuAge}</td>
</tr>
`
}).join("");
tab.innerHTML += str;
}
async function renderTable(){
let db = await openDB("stuDB",1);
let stuInfo = await getDataByKey(db, "stu");
render(stuInfo);
setInterval(async function(){
let stuInfo2 = await getDataByKey(db, "stu");
if(stuInfo2.length !== stuInfo.length){
stuInfo = stuInfo2;
render(stuInfo);
}
}, 1000);
}
renderTable()
</script>
</body>
</html>cookie 定时器轮询(setInterval)
我们可以通过定时器轮询的方式来监听cookie的变化,从而达到一个多标签也通信的目的。
js
// 页面1
document.cookie = "name=tom";js
// 页面2
let cookie = document.cookie;
setInterval(() =>{
if (cookie !== document.cookie) {
console.log('cookie发生改变了');
cookie = document.cookie;
}
})window.open window.postMessage
html
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>页面一</title>
</head>
<body>
<button id="popBtn">弹出新窗口</button>
<input type="text" id="content">
<button id="btn">发送数据</button>
<script>
const popBtn = document.querySelector("#popBtn");
const content = document.querySelector("#content");
const btn = document.querySelector("#btn");
let opener = null; // 用于保存 window.open 打开的窗口的引用
popBtn.onclick = function(){
opener = window.open(
"index2.html",
"123123",
"height=400,width=400,top=20,resizeable=yes"
);
}
btn.onclick = function(){
let data = {
value : content.value
}
// data 代表的是要发送的数据,第二个参数是 origin,使用 * 代表所有域
opener.postMessage(data, "*")
}
</script>
</body>
</html>html
<!-- index2.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>页面二</title>
</head>
<body>
<p>这是页面二</p>
<script>
window.addEventListener("message",function(e){
console.log(e.data);
})
</script>
</body>
</html>Websocket
Websocket协议在2008年诞生,在2011年成为国际标准。目前所有浏览器都已经支持了。
它的最大特点就是,客户端可以主动向服务器发送信息。
安装:npm i -save ws
js
// 首先获取到一个 WebSocketServer 类
var WebSocketServer = require("ws").Server;
// 创建 WebSocket 服务器
var wss = new WebSocketServer({
port : 3000
})
// 该数组用于保存所有的客户端连接实例
var clients = [];
// 当客户端连接上 WebSocket 服务器的时候
// 就会触发 connection 事件,该客户端的实例就会传入此回调函数
wss.on('connection', function(client){
// 将当前客户端连接实例保存到数组里面
clients.push(client);
console.log(`当前有${clients.length}个客户端在线...`);
// 给传入进来的客户端连接实例绑定一个 message 事件
client.on('message', function(msg){
console.log("收到的消息为:" + msg);
// 接下来需要将接收到的消息推送给其他所有的客户端
for(var c of clients){
if(c !== client){
c.send(msg.toString());
}
}
})
client.on('close',function(){
var index = clients.indexOf(this);
clients.splice(index, 1);
console.log(`当前有${clients.length}个客户端在线...`);
})
})
console.log("Web Socket 服务器已经启动....");html
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>页面一</title>
</head>
<body>
<input type="text" name="" id="msg">
<button id="send">发送信息</button>
<script>
// 建立 websocket 连接
var ws = new WebSocket("ws://localhost:3000");
var send = document.querySelector("#send");
var msg = document.querySelector("#msg");
send.onclick = function(){
if(msg.value.trim() !== ""){
ws.send(msg.value.trim());
}
}
// 关闭窗口或者窗口刷新的时候
// 要关闭当前的 websocket 连接
window.onbeforeunload = function(){
ws.close();
}
</script>
</body>
</html>html
<!-- index2.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
// 建立 websocket 连接
var ws = new WebSocket("ws://localhost:3000");
var count = 1; // 用于计数
ws.onopen = function () {
ws.onmessage = function (event) {
var oP = document.createElement("p");
oP.innerHTML = `第${count}次接收到消息:${event.data}`;
document.body.appendChild(oP);
count++;
}
}
// 关闭窗口或者窗口刷新的时候
// 要关闭当前的 websocket 连接
window.onbeforeunload = function () {
ws.close();
}
</script>
</body>
</html>Web Worker
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<p>计数:<output id="result"></output></p>
<button id="startBtn">开始工作</button>
<button id="stopBtn">停止工作</button>
<script>
const startBtn = document.getElementById("startBtn");
const stopBtn = document.getElementById("stopBtn");
let worker; // 用于存储 worker 线程
startBtn.onclick = function(){
worker = new Worker('worker.js');
worker.onmessage = function(event){
document.querySelector("#result").innerHTML = event.data;
}
}
stopBtn.onclick = function(){
worker.terminate();
worker = null;
}
</script>
</body>
</html>js
// woker.js
let count = 0;
setInterval(function(){
count++;
postMessage(count);
}, 1000);