1、Web Storage API(localStorage和storage事件)
会话存储 sessionStorage:页面会话期间可用(只要浏览器处于打开状态,包括页面重新加载和恢复),关闭后不再存储。
本地存储 localStorage:浏览器关闭仍然可用,没有过期日期,可以通过JS、清除浏览器缓存或本地存储来清除。(ios 5.1之后,移动端的safari将localStorage存储在cache文件中,操作系统会偶尔进行清除,特别是空间不足时)
支持的方法key(),setItem(),getItem(),removeItem(),clear(),可以直接.key取值
不同浏览器无法共享storage,相同浏览器的不同页面可以共享 localStorage,无法共享sessionStorage,但是同一个页面的同源iframe是可以共享 sessionStorage的。
注意是键值对是以字符串形式存储的,会自动转化为字符串类型,为了避免未知错误JSON.stringify()和JSON.parse()一下。
当前页面使用的storage被其他页面修改时会触发StorageEvent事件,也就是说事件在同一个域的不同页面之间触发,a页面注册了storage的监听函数,只有跟a页面同域名的b页面操作storage对象,a页面才会被触发storage事件,才会执行监听函数。a页面注册storage监听,是监听不到a页面的改变的。
localStorage里面存储的数据如果发生变化,会触发storage事件(包括新增、删除、更新,或者清空所有(清空只触发一次)),该事件是注册在window上的。同源的其他页面可以监听到此storage事件,而本页面是无法监听到此事件的(chrome测试如此)。
// a.js
const values = ["aa", "bb", "cc", "dd"];
let count = 0;
document.querySelector(".btn-local").addEventListener("click", () => {
localStorage.setItem("testlocal", values[count % 4]);
count++;
});
document.querySelector(".btn-session").addEventListener("click", () => {
sessionStorage.setItem("testsession", values[count % 4]);
count++;
});
document.querySelector(".btn-openb").addEventListener("click", () => {
window.open("b.html");
});
window.addEventListener("storage", (e) => {
console.log("触发storage事件", e);
});
// b.js
window.addEventListener("storage", (e) => {
console.log("触发storage事件", e);
});
</script>
e.key为item的名称,即setItem的第一个参数。新增item(setItem)的e.oldValue为null,e.newValue为新增的值。修改item(setItem)的e.oldValue为修改前的值,e.newValue为修改后的值。清空所有(clear)的e.key为null,e.oldValue为null,e.newValue也为null。
注意点:网上有说法是localStorage和sessionStorage都会触发storage事件,其实不然。只有localStorage才可以触发storage事件,chrome亲测如此。mdn文档亦如此表示 storage_event。其实,sessionStorage 只在会话有效,也就是说同源的其他页面是访问不到本页面的sessionStorage的,sessionStorage不共享,A页面的看不到B页面的sessionStorage,B页面也看不到A页面的sessionStorage,那么监听不到storage改变的事件也情有可原。
window.open()打开的新页面虽然会继承原同源页面的sessionStorage,但依旧监听不到storage。
2、window.postMessage
window.postMessage()可以安全的实现跨域通信,一般方法都是同源非跨域的。先window.open()打开页面b,并获得页面b的窗口引用 newwindow,用窗口引用 newwindow来发送消息。
newwindow.postMessage(message, targetOrigin, [transfer])
targetOrigin指明可以接听消息的窗口origin,*代表所有。最好指明具体的origin,防止消息泄露。
MDN window.postMessage
// a.js
const values = ["aa", "bb", "cc", "dd"];
let count = 0;
const newwindow = window.open("b.html");
newwindow.postMessage(
"newwindow, message from a.html,b" + values[count++ % 4],
"http://127.0.0.1:8081"
);
window.addEventListener("message", (e) => {
console.log("a.html 监听到了message事件:", e.data, e.origin, e);
});
// b.js
window.addEventListener("message", (e) => {
console.log("b.html 监听到了message事件:", e.data, e.origin, e);
if (e.origin === "http://127.0.0.1:8081") { //需要判断消息源,防止被攻击
e.source.postMessage(
"e.source, message from b.html," + values[count++ % 4]
);
}
});
3、Broadcast Channel API
同源浏览上下文(window、tab、frame、iframe)之间通信。创建具有相同名称的BroadcastChannel来订阅特定频道,进行双向通信。
页面a创建名为channelName1的频道,并发送消息:
// a.js
const bc = new BroadcastChannel("channelName1");
bc.postMessage("aaaa广播了一条消息");
页面b连接名为channelName1的频道,并接收消息:
// b.js
const bc = new BroadcastChannel("channelName1");
bc.onmessage = (e) => {
console.log("接收到来自a的消息:", e.data);
}
bc.close();// 断开与频道的连接,可以垃圾回收。
BroadcastChannel构造函数的入参是频道名称,相互通信的浏览器上下文要处于同一频道,即频道名称相同。不能接收自己页面的消息。
MDN Broadcast Channel API
4、Service Worker API(navigator.serviceWorker)
Service Worker充当Web应用程序、浏览器与网络之间的代理服务器,创建有效的离线体验、拦截网络请求。先注册一个 Service Worker代理服务器,每次页面postMessage的时候,这个代理服务器都会监听到消息,然后他再把message转发给注册了代理服务器的页面,页面就可以监听到了。所以是能接收自己页面的消息的。
// a.js
navigator.serviceWorker.register("/sw.js").then((registration) => { // 这里sw.js是位于origin的根目录下面的
console.log("a.html serviceWorker注册成功", registration);
navigator.serviceWorker.addEventListener("message", (e) => {
console.log("a.html 监听到了message事件:", e.data, e);
});
document.querySelector(".btn").addEventListener("click", () => {
navigator.serviceWorker.controller.postMessage(
"message from a.html, " + values[count++ % 4]
);
});
});
// b.js
navigator.serviceWorker.register("/sw.js").then((registration) => {
console.log("b.html serviceWorker注册成功", registration);
navigator.serviceWorker.addEventListener("message", (e) => { // 监听message
console.log("b.html 监听到了message事件:", e.data, e);
});
document.querySelector(".btn").addEventListener("click", () => {
navigator.serviceWorker.controller.postMessage( // 发送消息
"message from b.html, " + values[count++ % 4]
);
});
});
// sw.js
self.addEventListener("message", async (event) => {
const clients = await self.clients.matchAll();
console.log("sw.js:", event, clients, self);
clients.forEach(function (client) {
client.postMessage(event.data); // 转发消息
});
});
5、SharedWorker
如果要使SharedWorker连接到多个不同的页面,这些页面必须是同源的。调试shared worker的页面:chrome://inspect/#workers
// a.js
const worker = new SharedWorker(`worker.js`);
worker.port.onmessage = (e) => {
console.log("a.html 接收到message:", e.data, e);
};
document.querySelector(".btn").addEventListener("click", () => {
worker.port.postMessage("message from a.html," + values[count++ % 4]);
});
// b.js
const worker = new SharedWorker(`worker.js`);
worker.port.onmessage = (e) => {
console.log("b.html 接收到message:", e.data, e);
};
worker.port.postMessage("message from b.html," + values[count++ % 4]);
// worker.js
const ports = [];
function sendMessageToAllPorts(message) { //向所有连接到此sharedworker的页面都发送消息
console.log("sendMessageToAllPorts", ports);
ports.forEach(function (port) {
port.postMessage(message);
});
}
onconnect = function (event) {
const port = event.ports[0];
ports.push(port); //将连接到此sharedworker的页面存起来
port.onmessage = function (e) {
sendMessageToAllPorts("message from worker.js" + e.data);
};
};
MDN Shared Worker
serviceWorker和SharedWorker都属于Web Workers API
跨页面通信的应用:聊天室,跨页面登录等。