- 同源:如果两个页面的协议,域名和端口都相同,则两个页面具有相同的源。
- 同源策略:它是浏览器提供的一个安全功能,通俗的理解:浏览器规定,A网站的JavaScript,不允许和非同源的网站C之间,进行资源的交互。
- 跨域:两个URL的协议、域名、端口一项或多项不一样。
一、同源页面通信
1.Broadcast
Broadcast Channel API 是浏览器提供的跨标签页 / 跨上下文通信机制,专门用于同域名下多个页面(标签页、iframe、Worker)之间的双向实时通信,相比 localStorage 的 storage 事件,它更高效、支持复杂数据类型,且无需依赖存储。
核心概念
- 广播频道(Broadcast Channel):同域名下的所有页面可通过创建同名频道实现通信,相当于一个 “公共聊天室”。
- 双向通信:任意页面发送消息,所有订阅该频道的页面都能接收(包括自身)。
- 自动跨上下文:支持标签页、iframe、Service Worker、Web Worker 之间的通信(需同域名)
- 内存广播频道(无独立线程)
首页新建广播频道,子页建立相同名称的频道,并通过message监听即可。只要postMessage每次触发,就会监听到。
基本用法
1. 创建频道 + 发送消息(发送方)
// 1. 创建/连接名为 "my-channel" 的广播频道(同名频道会自动关联)
const bc = new BroadcastChannel('my-channel');
// 2. 发送消息(支持字符串、对象、数组等任意可序列化类型)
function sendMessage(data) {
bc.postMessage({
type: 'message', // 自定义消息类型(用于区分不同业务)
content: data,
sender: 'tab-1' // 可选:标识发送方
});
}
// 调用示例:发送字符串、对象、数组
sendMessage('Hello 所有标签页!');
sendMessage({ name: '张三', age: 20 });
sendMessage([1, 2, 3, '测试数据']);
2. 监听消息(接收方)
// 1. 连接同名频道(必须与发送方频道名一致)
const bc = new BroadcastChannel('my-channel');
// 2. 监听消息事件
bc.addEventListener('message', (event) => {
// event.data 为发送方传递的原始数据
console.log('收到广播消息:', event.data);
// 实际场景:根据消息类型处理业务
switch (event.data.type) {
case 'message':
alert(`来自 ${event.data.sender} 的消息:${event.data.content}`);
break;
case 'notice':
console.log('系统通知:', event.data.content);
break;
default:
break;
}
});
3. 关闭频道(避免内存泄漏)
// 页面卸载时关闭频道(重要!)
window.addEventListener('beforeunload', () => {
bc.close(); // 关闭后无法再发送/接收消息
});
语法
创建一个标识为xixi的频道:
const bc = new BroadcastChannel('xixi');
各个页面可以通过onmessage来监听被广播的消息
bc.onmessage = function (e) {
const data = e.data;
const text = '[receive] ' + data.msg + ' —— tab ' + data.from;
console.log('[BroadcastChannel] receive message:', text);
};
要发送消息时只需要调用实例上的postMessage方法即可:
bc.postMessage(mydata);
使用
- 第一个页面
<!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>
<button id="button">点击</button>
<script>
let button=document.getElementById('button')
button.onclick=function(){
let cast = new BroadcastChannel('mychannel'); //创建一个名字是mychannel的对象。记住这个名字,下面会用到
myObj = { from: "children1", content: "add" };
cast.postMessage(myObj);
}
</script>
</body>
</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>
let cast1 = new BroadcastChannel('mychannel');//创建一个和刚才的名字一样的对象
cast1.onmessage = function (e) {
//e.data就是刚才的信息;
console.log(e.data);
};
</script>
</body>
</html>
在第一个页面点击按钮,在第二个页面的控制台上即可查看第一个页面发送过来的信息。
关键特性
- 必要的情况可以将触发事件绑定到
window,子组件通过window.parent.事件名触发 - 同域名限制:仅支持 协议、域名、端口完全一致 的页面通信(跨域不生效,安全机制)。
- 支持的数据类型:
- 基本类型(字符串、数字、布尔值等)。
- 复杂类型(对象、数组、Date、RegExp 等,通过结构化克隆算法序列化)。
- ❌ 不支持函数、Symbol、循环引用对象。
- 自身也能接收消息:与 localStorage 的 storage 事件不同,当前页面发送的消息,自身也会触发 message 事件(可通过 sender 字段过滤自身消息)。
- 无需依赖存储:消息直接在内存中传递,不会持久化到本地(区别于 localStorage)。
2.storage事件监听
用于监听当前页面中 localStorage 或 sessionStorage 的数据变化,当其他页面(同域名)修改了存储数据时,当前页面会触发该事件。
当同源页面的某个页面修改了localStorage/sessionStorage,其余的同源页面只要注册了storage事件,就会触发,所以满足触发监听的条件有三个:
- 同一浏览器打开了两个同源页面
- 其中一个网页修改了localStorage
- 另一网页注册了storage事件
注意:在同一个网页修改本地存储,又在同一个网页监听,这样是没有效果的。
使用
监听 storage 事件(接收消息)
// 监听 localStorage 变化
window.addEventListener("storage", (e) => {
// e 对象包含存储变化的详细信息:
// - key: 被修改的存储键名
// - oldValue: 修改前的值
// - newValue: 修改后的值
// - storageArea: 被修改的存储对象(localStorage 或 sessionStorage)
// - url: 触发变化的页面 URL
console.log(`存储键 ${e.key} 发生变化`);
console.log("旧值:", e.oldValue);
console.log("新值:", e.newValue);
console.log("来源页面:", e.url);
// 实际场景中可根据 key 过滤处理
if (e.key === "crossTabMsg") {
alert(`收到其他页面的消息:${e.newValue}`);
}
});
触发 storage 事件(发送消息)
在另一个同域名的页面中,通过修改 localStorage 触发事件:
// 发送消息(修改 localStorage 触发事件)
function sendCrossTabMessage(message) {
// 存储数据到 localStorage,会触发其他页面的 storage 事件
localStorage.setItem("crossTabMsg", message);
// 可选:若不需要保留数据,可立即删除(事件仍会触发)
// setTimeout(() => {
// localStorage.removeItem("crossTabMsg");
// }, 0);
}
// 调用示例:发送消息给其他标签页
sendCrossTabMessage("Hello from Tab 1!");
举例:根据传值让B页面控制A页面刷新
// A页面
window.addEventListener('storage', function (e) {
console.log('11111111', e);
});
// B页面
localStorage.clear();
localStorage.setItem('key123', 'value123');
根据传值让B页面控制A页面刷新
// A页面
window.addEventListener('storage', function (e) {
if(e.key ==='key123'){
window.location.reload()
}
});
// B页面
localStorage.setItem('key123', 'value123');
关键特性与注意事项
- 仅监听 “其他页面” 的修改:当前页面修改
localStorage时,不会触发自身的storage事件,只有同域名的其他标签页修改时才会触发。 - 适用场景:多标签页间的简单通信(如登录状态同步、消息通知),无需复杂逻辑时非常实用。
- 数据格式限制:
localStorage仅支持字符串,若传递对象需先序列化(如JSON.stringify),接收时再解析(JSON.parse):
// 发送对象
localStorage.setItem("user", JSON.stringify({ name: "张三", age: 20 }));
// 接收对象
window.addEventListener("storage", (e) => {
if (e.key === "user") {
const user = JSON.parse(e.newValue);
console.log(user.name); // 张三
}
});
- 跨域限制:仅同域名(协议、域名、端口完全一致)的页面才能通过
storage事件通信,跨域页面修改存储不会触发事件。
通过 storage 事件,可轻松实现多标签页间的轻量级数据同步,适合简单场景下的跨标签页通信。
如果非得要在同一网页监听怎么办?可以重写localStorage的方法,如下:
var orignalSetItem = localStorage.setItem;
localStorage.setItem = function (key, newValue) {
var setItemEvent = new Event("setItemEvent");
setItemEvent.newValue = newValue;
window.dispatchEvent(setItemEvent);
orignalSetItem.apply(this, arguments);
}
window.addEventListener("setItemEvent", function (e) {
console.log(e.key + '---' + e.newValue);
});
localStorage.setItem("aaa", "111");
3.postMessage
- 是用于监听跨窗口 / 跨域通信消息的 API,常用于不同页面(如 iframe 与父页面、多窗口间)的安全数据传递。
使用
同源页面使用举例:
基本用法
// 监听 message 事件
window.addEventListener('message', function(event) {
// event 对象包含以下关键属性:
// - data: 发送过来的数据(任意类型,如字符串、对象等)
// - origin: 发送方的源(协议 + 域名 + 端口,如 "https://example.com:8080")
// - source: 发送消息的窗口对象(可用于回传消息)
// 1. 验证发送方 origin(安全校验,必须做!)
if (event.origin !== 'https://trusted-domain.com') {
return; // 忽略非信任源的消息
}
// 2. 处理接收到的数据
console.log('收到消息:', event.data);
// 3. 可选:向发送方回传消息
event.source.postMessage('已收到消息', event.origin);
});
发送消息的对应方法
- 发送方通过
otherWindow.postMessage()发送消息:
// 向 iframe 发送消息(假设 iframe 的 id 为 "myIframe")
const iframe = document.getElementById('myIframe');
iframe.contentWindow.postMessage({ type: 'greeting', content: 'Hello' }, 'https://trusted-domain.com');
// 向父窗口发送消息(iframe 内部)
window.parent.postMessage('来自 iframe 的消息', 'https://parent-domain.com');
关键注意事项
- 安全校验必须做:始终通过
event.origin验证发送方的合法性,避免接收恶意域名的消息,防止 XSS 或数据泄露。 - 跨域限制:
- 即使域名不同,
postMessage仍可发送消息,但接收方需显式监听并验证origin。 event.source.postMessage回传时,第二个参数建议指定明确的origin(如event.origin),而非*(不安全)。
- 即使域名不同,
- 数据类型限制 传递的数据会被结构化克隆算法序列化,支持大部分类型(对象、数组、基本类型等),但不支持函数、Symbol 等。
典型应用场景
- iframe 与父页面之间的数据交互(如嵌入第三方组件时传递参数)。
- 多窗口间的通信(如通过
window.open打开的新窗口与原窗口通信)。 - 页面与 Service Worker 或 Web Worker 之间的消息传递(扩展用法)。
4.sharedworker
SharedWorker 是浏览器提供的跨标签页 / 跨上下文共享线程的通信方案,与 Broadcast Channel 不同,它通过独立的后台线程实现多页面数据共享和逻辑复用,适合需要复杂计算、状态统一管理的跨页面通信场景。
SharedWorker 适合需要全局状态管理或复杂后台逻辑的跨页面通信场景,相比 Broadcast Channel,它提供了独立线程和状态持久化能力,尤其适合多页面协同、实时数据处理等场景。如果你的需求只是简单的消息广播(如登录通知),Broadcast Channel 更简洁;如果需要统一管理状态、避免重复请求 / 连接,SharedWorker 是更优选择。
核心概念
- 共享线程:创建一个独立于页面的后台线程,同域名下的所有页面(标签页、iframe)可连接该线程,实现数据共享和通信。
- 双向通信:页面与 SharedWorker 之间通过 postMessage 发送消息,通过 onmessage 监听响应,支持任意可序列化数据。
- 状态持久化:SharedWorker 线程在多个页面连接期间持续运行,可维护全局状态(如用户登录信息、实时数据缓存),页面刷新 / 重新打开不影响线程状态。
- 同域名限制:仅支持协议、域名、端口完全一致的页面连接(跨域页面无法访问)。
基本用法
第一步:创建 SharedWorker 线程文件(独立 JS 文件)
新建 shared-worker.js(注意:需通过服务器访问,本地文件 file:// 协议可能报错):
// shared-worker.js:共享线程核心逻辑
let connections = []; // 存储所有连接的页面
let globalState = {
userInfo: null,
onlineCount: 0 // 在线页面数量
};
// 监听页面连接
self.addEventListener('connect', (e) => {
const port = e.ports[0]; // 获取通信端口(每个页面对应一个端口)
connections.push(port); // 记录连接
globalState.onlineCount = connections.length; // 更新在线数
// 监听页面发送的消息
port.addEventListener('message', (event) => {
const { type, data } = event.data;
// 处理不同类型的消息
switch (type) {
// 登录状态同
case 'login':
globalState.userInfo = data.userInfo;
// 向所有连接的页面广播登录状态
broadcastMessage({
type: 'login-success',
data: { userInfo: globalState.userInfo, onlineCount: globalState.onlineCount }
});
break;
// 获取当前全局状态
case 'get-state':
port.postMessage({
type: 'state-response',
data: globalState
});
break;
// 自定义消息广播
case 'broadcast':
broadcastMessage({
type: 'custom-message',
data: { content: data.content, sender: data.sender }
});
break;
}
});
// 监听页面断开连接
port.addEventListener('close', () => {
connections = connections.filter(conn => conn !== port); // 移除断开的连接
globalState.onlineCount = connections.length;
// 广播在线人数更新
broadcastMessage({
type: 'online-count-update',
data: { onlineCount: globalState.onlineCount }
});
});
// 启动端口通信(必须调用)
port.start();
});
// 向所有连接的页面广播消息
function broadcastMessage(message) {
connections.forEach(port => {
port.postMessage(message);
});
}
第二步:页面连接 SharedWorker 并通信(任意同源页面)
// 页面端代码(可在多个标签页中复用)
let worker = null;
// 初始化 SharedWorker 连接
function initSharedWorker() {
// 检测浏览器支持(Chrome 4+、Firefox 29+、Edge 79+,不支持 IE)
if (!window.SharedWorker) {
console.warn('当前浏览器不支持 SharedWorker');
return;
}
// 连接共享线程(参数为线程文件路径)
worker = new SharedWorker('./shared-worker.js');
// 监听 SharedWorker 发送的消息
worker.port.addEventListener('message', (event) => {
const { type, data } = event.data;
console.log('收到 SharedWorker 消息:', type, data);
// 处理业务逻辑
switch (type) {
case 'login-success':
// 更新页面登录状态
document.getElementById('user-name').textContent = data.userInfo.name;
document.getElementById('online-count').textContent = data.onlineCount;
break;
case 'online-count-update':
// 更新在线人数
document.getElementById('online-count').textContent = data.onlineCount;
break;
case 'custom-message':
// 显示自定义广播消息
alert(`收到消息:${data.content}(来自 ${data.sender})`);
break;
}
});
// 启动端口通信(必须调用)
worker.port.start();
}
// 页面发送消息到 SharedWorker
function sendMessageToWorker(type, data) {
if (!worker) return;
worker.port.postMessage({ type, data });
}
// 示例:登录操作(一个标签页登录,所有页面同步)
function login(userInfo) {
sendMessageToWorker('login', { userInfo });
}
// 示例:获取全局状态(如页面加载时获取当前登录状态)
function getGlobalState() {
sendMessageToWorker('get-state', {});
}
// 示例:发送自定义广播消息
function sendBroadcast(content) {
const sender = Math.random().toString(36).substring(2, 10); // 页面唯一标识
sendMessageToWorker('broadcast', { content, sender });
}
// 页面加载时初始化
window.addEventListener('load', () => {
initSharedWorker();
getGlobalState(); // 加载时获取当前状态
});
页面 HTML 结构(示例)
<!DOCTYPE html>
<html>
<body>
<div>当前用户:<span id="user-name">未登录</span></div>
<div>在线标签页数:<span id="online-count">0</span></div>
<button onclick="login({ name: '张三', id: 1001 })">模拟登录</button>
<button onclick="sendBroadcast('Hello 所有页面!')">发送广播</button>
<script src="页面端代码.js"></script>
</body>
</html>
关键特性
- 独立线程:SharedWorker 运行在单独的后台线程,不阻塞页面主线程(适合处理复杂计算、实时数据监听)。
- 全局状态管理:可在线程中维护全局状态(如用户信息、配置数据),所有页面共享同一状态,无需重复请求后端。
- 通信机制:
- 页面 ↔ SharedWorker:通过 port.postMessage 发送消息,port.onmessage 监听。
- SharedWorker ↔ 所有页面:通过遍历连接端口 broadcastMessage 实现广播。
- 数据类型支持:与 Broadcast Channel 一致,支持基本类型、对象、数组、Date 等(结构化克隆算法),不支持函数、Symbol、循环引用。
- 生命周期:
- 当所有页面断开连接(关闭标签页 / 刷新),SharedWorker 线程会被浏览器销毁。
- 线程销毁后,全局状态丢失(如需持久化,需结合 localStorage/IndexedDB)。
常见应用场景
- 全局登录状态管理:一个标签页登录后,SharedWorker 广播状态,所有页面自动更新登录信息,无需后端查询。
- 实时数据同步:如股票行情、聊天消息,通过 SharedWorker 监听 WebSocket 连接,所有页面共享实时数据(避免多页面重复建立 WebSocket)。
- 多页面协同编辑:如在线文档,SharedWorker 维护文档最新内容,同步所有编辑页面的操作(如光标位置、修改内容)。
- 在线人数统计:通过 connections 数组记录连接的页面数,实时广播在线人数。
注意事项与踩坑指南
- 必须通过服务器访问:本地文件 file:// 协议下,SharedWorker 可能因安全限制无法创建,需通过 http:// 协议(如本地服务器、Nginx)访问。
- 调试方式:
- Chrome:F12 → More Tools → Application → Shared Workers,可查看运行的线程并调试。
- Firefox:F12 → Debugger → Workers,选择对应的 SharedWorker 进行调试。
- 跨域限制:同域名(协议 + 域名 + 端口)是硬性要求,跨域页面无法连接同一 SharedWorker。
- 内存泄漏:
- 页面关闭时,SharedWorker 会自动移除连接,但建议在页面 beforeunload 时主动关闭端口:
window.addEventListener('beforeunload', () => {
worker.port.close(); // 关闭当前页面与线程的连接
});
- 浏览器兼容性:不支持 IE 浏览器,Edge 需 79+ 版本,如需兼容低版本浏览器,可降级为 Broadcast Channel 或 localStorage。
- 线程重启:当所有页面断开连接后,SharedWorker 线程会被销毁,再次连接时会重新初始化(全局状态重置),如需持久化状态,可结合 localStorage:
// shared-worker.js 中持久化状态
function saveState() {
localStorage.setItem('globalState', JSON.stringify(globalState));
}
function loadState() {
const saved = localStorage.getItem('globalState');
if (saved) globalState = JSON.parse(saved);
}
// 线程初始化时加载状态
loadState();
5.serviceworker
Service Worker—— 它虽核心定位是 “离线缓存 + 后台同步”,但也支持跨页面通信,且具备独特的 “后台运行” 和 “离线能力”,与 SharedWorker 形成互补。
Service Worker 的核心优势在于 “离线能力” 和 “后台运行”,如果你的场景需要兼顾跨页面通信和离线体验,它是最佳选择;若仅需简单的跨页面数据共享,Broadcast Channel 或 SharedWorker 更轻量。
核心概念
- 独立后台线程:运行在页面主线程之外,无 DOM 访问权限,生命周期与页面无关(页面关闭后仍可运行)。
- 核心功能:离线资源缓存、后台同步、推送通知、跨页面通信(基于 postMessage)。
- 同域名限制:仅支持协议、域名、端口完全一致的页面(HTTPS 环境或 localhost 开发环境)。
- 生命周期:安装(Install)→ 激活(Activate)→ 等待 / 运行(Waiting/Running)→ 销毁(Terminated)。
基本用法:跨页面通信 + 离线缓存
第一步:注册 Service Worker(页面端)
// 页面端:注册 Service Worker
function registerServiceWorker() {
// 检测浏览器支持
if (!('serviceWorker' in navigator)) {
console.warn('当前浏览器不支持 Service Worker');
return;
}
navigator.serviceWorker.register('./service-worker.js')
.then(registration => {
console.log('Service Worker 注册成功:', registration.scope);
// 页面与 Service Worker 通信(关键:通过 registration.active.postMessage)
if (registration.active) {
// 存储通信端口,方便后续发送消息
window.swRegistration = registration;
sendMessageToSW({ type: 'register', data: { pageId: getPageIdentifier() } });
}
// 监听 Service Worker 发送的消息
navigator.serviceWorker.addEventListener('message', (event) => {
const { type, data } = event.data;
console.log('收到 Service Worker 消息:', type, data);
handleSWMessage(type, data);
});
})
.catch(error => {
console.error('Service Worker 注册失败:', error);
});
}
// 页面发送消息到 Service Worker
function sendMessageToSW(message) {
if (!window.swRegistration?.active) return;
window.swRegistration.active.postMessage(message);
}
// 生成页面唯一标识
function getPageIdentifier() {
return window.pageId || (window.pageId = Math.random().toString(36).slice(2, 10));
}
// 处理 Service Worker 消息
function handleSWMessage(type, data) {
switch (type) {
case 'broadcast':
alert(`收到广播消息:${data.content}(来自 ${data.sender})`);
break;
case 'online-count':
document.getElementById('online-count').textContent = data.count;
break;
case 'offline-notice':
alert('当前已离线,数据将在联网后同步');
break;
}
}
// 页面加载时注册
window.addEventListener('load', registerServiceWorker);
第二步:实现 Service Worker 核心逻辑(独立 JS 文件:service-worker.js)
// service-worker.js:核心逻辑(离线缓存+跨页面通信)
const CACHE_NAME = 'app-cache-v1'; // 缓存版本
const CACHE_FILES = [ // 需要缓存的离线资源
'/',
'/index.html',
'/styles.css',
'/script.js',
'/favicon.ico'
];
// 存储所有连接的页面客户端(用于广播)
let clientsList = [];
// 1. 安装阶段:缓存离线资源
self.addEventListener('install', (event) => {
console.log('Service Worker 安装中...');
// 等待缓存完成后再激活
event.waitUntil(
caches.open(CACHE_NAME)
.then(cache => cache.addAll(CACHE_FILES))
.then(() => self.skipWaiting()) // 跳过等待,直接激活
);
});
// 2. 激活阶段:清理旧缓存+获取所有客户端
self.addEventListener('activate', (event) => {
console.log('Service Worker 已激活');
// 清理旧版本缓存
event.waitUntil(
caches.keys()
.then(cacheNames => {
return Promise.all(
cacheNames.filter(name => name !== CACHE_NAME).map(name => caches.delete(name))
);
})
.then(() => self.clients.claim()) // 控制所有打开的页面
);
// 获取所有已连接的客户端(页面)
self.clients.matchAll().then(clients => {
clientsList = clients;
});
});
// 3. 拦截网络请求:优先使用缓存(离线核心)
self.addEventListener('fetch', (event) => {
// 仅拦截同源的 GET 请求(可根据需求调整)
if (event.request.method === 'GET' && event.request.mode === 'same-origin') {
event.respondWith(
caches.match(event.request)
.then(cachedResponse => {
// 缓存优先,同时更新缓存(重新请求网络并缓存最新资源)
const networkResponse = fetch(event.request).then(response => {
caches.open(CACHE_NAME).then(cache => {
cache.put(event.request, response.clone());
});
return response;
});
// 离线时返回缓存,在线时返回网络资源(更新缓存)
return cachedResponse || networkResponse;
})
);
}
});
// 4. 跨页面通信:监听页面消息+广播
self.addEventListener('message', (event) => {
const { type, data } = event.data;
const senderClient = event.source; // 发送消息的页面客户端
switch (type) {
// 页面注册时,记录客户端
case 'register':
if (!clientsList.includes(senderClient)) {
clientsList.push(senderClient);
// 广播在线人数
broadcastMessage({
type: 'online-count',
data: { count: clientsList.length }
});
}
break;
// 页面发送广播消息,转发给所有其他页面
case 'broadcast':
broadcastMessage({
type: 'broadcast',
data: {
content: data.content,
sender: data.sender
}
}, senderClient); // 排除发送方
break;
// 离线同步请求(示例:保存表单数据)
case 'sync-data':
// 检查网络状态
if (navigator.onLine) {
// 联网时直接同步
syncDataToServer(data).then(() => {
senderClient.postMessage({ type: 'sync-success', data: { msg: '数据同步成功' } });
});
} else {
// 离线时存入 IndexedDB,等待后台同步
saveToIndexedDB('syncQueue', data).then(() => {
senderClient.postMessage({ type: 'offline-notice' });
});
}
break;
}
});
// 5. 后台同步:离线时缓存的请求,联网后自动同步
self.addEventListener('sync', (event) => {
if (event.tag === 'sync-data-queue') { // 与页面注册的同步标签一致
event.waitUntil(
getFromIndexedDB('syncQueue')
.then(queue => Promise.all(queue.map(data => syncDataToServer(data))))
.then(() => clearIndexedDB('syncQueue')) // 同步完成后清空队列
);
}
});
// 工具函数:广播消息给所有客户端(可选排除发送方)
function broadcastMessage(message, excludeClient = null) {
clientsList.forEach(client => {
if (client !== excludeClient && client.visibilityState !== 'hidden') {
client.postMessage(message);
}
});
}
// 工具函数:模拟数据同步到服务器
function syncDataToServer(data) {
return new Promise((resolve) => {
setTimeout(() => {
console.log('数据同步到服务器:', data);
resolve();
}, 1000);
});
}
// 工具函数:IndexedDB 操作(离线数据存储)
const IDB = {
dbName: 'ServiceWorkerDB',
version: 1
};
function openIndexedDB() {
return new Promise((resolve, reject) => {
const request = indexedDB.open(IDB.dbName, IDB.version);
request.onupgradeneeded = (e) => {
const db = e.target.result;
// 创建存储对象(用于保存同步队列)
if (!db.objectStoreNames.contains('syncQueue')) {
db.createObjectStore('syncQueue', { keyPath: 'id', autoIncrement: true });
}
};
request.onsuccess = (e) => resolve(e.target.result);
request.onerror = (e) => reject(e.target.error);
});
}
function saveToIndexedDB(storeName, data) {
return openIndexedDB().then(db => {
return new Promise((resolve) => {
const tx = db.transaction(storeName, 'readwrite');
const store = tx.objectStore(storeName);
store.add({ ...data, timestamp: Date.now() });
tx.oncomplete = () => {
db.close();
resolve();
};
});
});
}
function getFromIndexedDB(storeName) {
return openIndexedDB().then(db => {
return new Promise((resolve) => {
const tx = db.transaction(storeName, 'readonly');
const store = tx.objectStore(storeName);
const request = store.getAll();
request.onsuccess = (e) => {
db.close();
resolve(e.target.result);
};
});
});
}
function clearIndexedDB(storeName) {
return openIndexedDB().then(db => {
return new Promise((resolve) => {
const tx = db.transaction(storeName, 'readwrite');
const store = tx.objectStore(storeName);
store.clear();
tx.oncomplete = () => {
db.close();
resolve();
};
});
});
}
核心功能详解
跨页面通信机制
- 页面 → Service Worker:通过 registration.active.postMessage 发送消息。
- Service Worker → 页面:通过 client.postMessage 发送消息(client 是页面客户端实例)。
- 广播实现:Service Worker 维护 clientsList 存储所有连接的页面,遍历发送消息即可实现 “一对多广播”。
离线缓存核心
- 安装阶段:缓存静态资源(HTML、CSS、JS、图片等),确保离线时可访问。
- 请求拦截:通过 fetch 事件拦截网络请求,优先返回缓存资源,在线时后台更新缓存。
- 缓存更新:通过 activate 事件清理旧版本缓存,避免资源冲突。
后台同步
- 离线时通过 IndexedDB 存储需要同步的请求(如表单提交、数据上传)。
- 联网后,Service Worker 触发 sync 事件,自动同步缓存的请求到服务器。
典型应用场景
- 离线 Web 应用:如文档编辑、新闻阅读类应用,离线时可访问缓存内容,联网后同步数据。
- 推送通知:即使页面关闭,Service Worker 仍可接收服务器推送的通知(需配合 Push API)。
- 跨页面状态同步:如登录状态、系统配置更新,通过 Service Worker 广播给所有页面。
- 后台数据同步:离线时用户操作的数据(如表单、评论),联网后自动同步,提升用户体验。
6.webworker
Web Worker—— 它是浏览器提供的 “前端多线程” 方案,核心用于解放主线程(避免复杂计算阻塞 UI),与 Service Worker、SharedWorker 定位互补。
核心概念
- 独立线程:运行在页面主线程之外的后台线程,专门处理耗时操作(如复杂计算、大数据处理),不阻塞 UI 渲染。
- 通信机制:主线程与 Worker 线程通过 postMessage 发送消息,onmessage 监听响应,数据通过 “结构化克隆算法” 序列化传递(不共享内存)。
- 运行限制:
- 无 DOM 访问权限(不能操作 document、window);
- 不能访问主线程的全局变量(如 localStorage 可访问,但需注意线程安全);
- 仅支持同源脚本(Worker 脚本文件需与主线程页面同域名)。
- 生命周期:主线程创建 Worker 后,线程持续运行,直到主线程调用 worker.terminate() 或 Worker 自身调用 self.close()。
基本用法
第一步:创建 Worker 脚本文件(独立 JS 文件:worker.js)
// worker.js:Worker 线程核心逻辑(不能操作 DOM)
console.log('Worker 线程启动');
// 监听主线程发送的消息
self.addEventListener('message', (event) => {
const { type, data } = event.data;
switch (type) {
// 处理复杂计算任务
case 'compute':
const result = heavyComputation(data.num); // 耗时计算
// 向主线程发送结果
self.postMessage({
type: 'compute-result',
data: { result, taskId: data.taskId }
});
break;
// 终止 Worker 线程
case 'terminate':
self.close(); // 关闭自身线程
break;
}
});
// 模拟耗时计算(如大数据排序、数学运算)
function heavyComputation(num) {
let result = 0;
for (let i = 0; i < num * 10000000; i++) {
result += Math.sqrt(i);
}
return result.toFixed(2);
}
// Worker 线程关闭时触发
self.addEventListener('close', () => {
console.log('Worker 线程已关闭');
});
第二步:主线程(页面端)操作 Worker
// 页面端:创建 Worker 并通信
let worker = null;
// 初始化 Worker
function initWorker() {
// 检测浏览器支持(Chrome 4+、Firefox 3.5+、Edge 12+,不支持 IE)
if (!window.Worker) {
console.warn('当前浏览器不支持 Web Worker');
return;
}
// 创建 Worker 实例(参数为 Worker 脚本路径)
worker = new Worker('./worker.js');
// 监听 Worker 发送的消息
worker.addEventListener('message', (event) => {
const { type, data } = event.data;
console.log('主线程收到 Worker 消息:', type, data);
switch (type) {
case 'compute-result':
// 更新 UI 显示计算结果(主线程可操作 DOM)
document.getElementById(`result-${data.taskId}`).textContent = data.result;
break;
}
});
// 监听 Worker 错误
worker.addEventListener('error', (error) => {
console.error(`Worker 错误:${error.message}(行号:${error.lineno})`);
worker.terminate(); // 出错后终止 Worker
});
}
// 主线程发送任务给 Worker
function sendTaskToWorker(num) {
if (!worker) return;
const taskId = Math.random().toString(36).slice(2, 8); // 生成任务ID
// 插入结果显示节点
const resultDom = document.createElement('div');
resultDom.id = `result-${taskId}`;
resultDom.textContent = `任务 ${taskId} 执行中...`;
document.body.appendChild(resultDom);
// 发送消息给 Worker
worker.postMessage({
type: 'compute',
data: { num, taskId }
});
}
// 终止 Worker 线程(页面卸载或不需要时调用)
function terminateWorker() {
if (worker) {
worker.postMessage({ type: 'terminate' });
worker.terminate(); // 主线程强制终止 Worker(不可逆)
worker = null;
}
}
// 页面加载时初始化
window.addEventListener('load', initWorker);
// 页面卸载时清理 Worker
window.addEventListener('beforeunload', terminateWorker);
页面 HTML 结构(示例)
<!DOCTYPE html>
<html>
<body>
<h3>Web Worker 耗时计算示例</h3>
<button onclick="sendTaskToWorker(5)">执行计算任务(num=5)</button>
<button onclick="sendTaskToWorker(10)">执行计算任务(num=10)</button>
<button onclick="terminateWorker()">终止 Worker 线程</button>
<div id="result-container"></div>
<script src="页面端代码.js"></script>
</body>
</html>
核心功能详解
通信机制 主线程 → Worker 线程:worker.postMessage(data),data 支持基本类型、对象、数组等(结构化克隆算法),不支持函数、Symbol。 Worker 线程 → 主线程:self.postMessage(data),self 指向 Worker 全局对象。 数据传递特性:数据是 “复制传递” 而非 “引用传递”,主线程和 Worker 线程的变量互不影响(避免线程安全问题)。
错误处理
- Worker 线程内的错误不会阻塞主线程,需通过 worker.addEventListener('error', callback) 监听,错误对象包含 message(错误信息)、lineno(行号)、filename(Worker 脚本文件名)。
- 主线程可在错误后调用 worker.terminate() 终止 Worker,避免资源泄漏。
Worker 线程的全局对象与可用 API
- 全局对象:self(而非 window),可访问 console、setTimeout、setInterval 等。
- 可用 API:
- 网络请求:fetch、XMLHttpRequest;
- 存储:localStorage、sessionStorage、IndexedDB;
- 其他:URL.createObjectURL、Blob 等。
- 不可用 API:document、window、DOM 操作、alert、confirm 等
典型应用场景
- 复杂数学计算:如大数据排序、矩阵运算、加密解密(如 AES、RSA)。
- 数据处理:如解析大型 CSV/JSON 文件、数据可视化图表的渲染计算(如 ECharts 大数据量渲染)。
- 后台任务:如轮询接口获取实时数据(不阻塞主线程 UI 交互)。
- 音视频处理:如音频解码、视频帧分析(轻量级场景)。
进阶用法:创建多个 Worker 实现并行计算
当单个 Worker 无法满足需求时,可创建多个 Worker 实现并行计算:
// 主线程:创建 Worker 池
const WorkerPool = {
poolSize: 3, // 并发 Worker 数量
workers: [],
init() {
for (let i = 0; i < this.poolSize; i++) {
const worker = new Worker('./worker.js');
worker.addEventListener('message', this.handleMessage.bind(this));
this.workers.push(worker);
}
},
handleMessage(event) {
// 统一处理所有 Worker 的消息
console.log('Worker 池收到结果:', event.data);
// 更新 UI 逻辑...
},
sendTask(data) {
// 简单负载均衡:将任务分配给第一个空闲 Worker(需扩展空闲状态管理)
const idleWorker = this.workers[0];
idleWorker.postMessage(data);
}
};
// 初始化 Worker 池
WorkerPool.init();
// 发送并行任务
WorkerPool.sendTask({ type: 'compute', data: { num: 5, taskId: 'task1' } });
WorkerPool.sendTask({ type: 'compute', data: { num: 8, taskId: 'task2' } });
WorkerPool.sendTask({ type: 'compute', data: { num: 12, taskId: 'task3' } });
注意事项与踩坑指南
- 同源限制:Worker 脚本文件必须与主线程页面同域名(协议、域名、端口一致),不能加载跨域脚本。
- 本地文件运行问题:file:// 协议下,浏览器可能因安全限制禁止创建 Worker,需通过服务器(如 http://localhost)访问。
- 线程数量控制:避免创建过多 Worker(建议不超过 CPU 核心数),否则会导致线程切换开销,反而降低性能。
- 数据传递开销:大量数据传递(如超大对象、二进制数据)会产生序列化 / 反序列化开销,可通过 Transferable Objects 优化(转移数据所有权,避免复制):
// 主线程:转移 ArrayBuffer 所有权给 Worker(主线程后续无法访问该 buffer)
const buffer = new ArrayBuffer(1024);
worker.postMessage({ buffer }, [buffer]); // 第二个参数指定转移对象
调试方式:
- Chrome:F12 → Sources → Workers,可查看所有运行的 Worker 脚本,设置断点调试。
- Firefox:F12 → Debugger → Workers,选择对应的 Worker 进行调试。 浏览器兼容性:
- 不支持 IE 浏览器,Edge 需 12+ 版本,移动端支持 Chrome for Android、Safari on iOS 5.1+。
二、跨域页面通信
1.postMessage
postMessage 可用于解决以下方面的问题:
- 页面和其打开的新窗口的数据传递
- 页面与嵌套的 iframe 消息传递
- 多窗口之间消息传递
- 是用于监听跨窗口 / 跨域通信消息的 API,常用于不同页面(如 iframe 与父页面、多窗口间)的安全数据传递。
window.postMessage完全支持同源页面通信,它的核心设计是 “跨源通信”,但并不限制同源场景 —— 反而在同源下使用更简单(无需严格的origin校验),且能作为 Broadcast Channel API 的替代方案,尤其适合需要精准定向通信的场景。
语法
otherWindow.postMessage(message, targetOrigin, [transfer]);
-
otherWindow:其他窗口的一个引用,比如 iframe 的 contentWindow 属性、执行 window.open 返回的窗口对象、或者是命名过或数值索引的 window.frames。 -
message:要发送的数据。它将会被结构化克隆算法序列化,所以无需自己序列化(部分低版本浏览器只支持字符串,所以发送的数据最好用JSON.stringify() 序列化)。 -
targetOrigin:通过 targetOrigin 属性来指定哪些窗口能接收到消息事件,其值可以是字符串“*”(表示无限制)或者一个 URI(如果要指定和当前窗口同源的话可设置为"/")。在发送消息的时候,如果目标窗口的协议、主机地址或端口号这三者的任意一项不匹配 targetOrigin 提供的值,那么消息就不会发送。 -
执行如下代码,其他 window 可以监听派遣的 message 获取发送过来的数据:
window.addEventListener("message", (event)=>{
var origin = event.origin
if (origin !== "http://example.org:8080")
return;
// ...
}, false);
- 使用 addEventListener 绑定事件,如果代码走多次,会导致监听绑定多次,在VUE中,当组件销毁时需要调用 removeEventListener 移除监听\
- 还可以使用另一种监听写法,则不会重复绑定监听事件
window.onmessage=function(event){
var origin = event.origin
if (origin !== "http://example.org:8080")
return;
// ...
}
event 的属性有:
- data: 从其他 window 传递过来的数据
- origin: 调用 postMessage 时,消息发送窗口的 origin。例如:“example.com:8080”。
- source: 对发送消息的窗口对象的引用。可以使用此来在具有不同 origin 的两个窗口之间建立双向数据通信。
使用
通过window.open打开的页面
- 父传子
// 父页面
const targetWindow = window.open('http://www.bbb.com');
setTimeout(()=>{
targetWindow.postMessage('父传子', 'http://www.bbb.com')
}, 3000)
// 子页面
window.addEventListener('message', (e) => {
console.log(e.data)
})
- 子传父
//父页面
window.open('http://www.bbb.com');
window.addEventListener('message', (e) => {
console.log(e.data)
})
//子页面
window.opener.postMessage('子传父', 'http://www.aaa.com')
两个窗口之间建立双向数据通信
/**
* localhost:10002/index页面
**/
// 接收消息
window.addEventListener('message', (e) => {
console.log(e.data)
})
// 发送消息
const targetWindow = window.open('http://localhost:10001/user');
setTimeout(()=>{
targetWindow.postMessage('来自10002的消息', 'http://localhost:10001')
}, 3000)
/**
* localhost:10001/user页面
**/
window.addEventListener('message', (e) => {
console.log(e.data)
if (event.origin !== "http://localhost:10002")
return;
e.source.postMessage('来自10001的消息', e.origin)
})
页面与嵌套的 iframe 消息传递
http://www.domain1.com/a.html
<iframe id="iframe" src="http://www.domain2.com/b.html"></iframe>
<script>
var iframe = document.getElementById('iframe');
iframe.onload = function() {
// 向domain2发送跨域数据
iframe.contentWindow.postMessage('来自domain1的消息', 'http://www.domain2.com');
};
// 接受domain2返回数据
window.addEventListener('message',(e) => {
console.log(e.data);
}, false);
</script>
http://www.domain2.com/b.html
<script>
// 接收domain1的数据
window.addEventListener('message',(e) => {
console.log(e.data);
if(e.origin !== 'http://www.domain1.com')
return;
// 发送消息给domain1
window.parent.postMessage('来自domain2的消息', e.origin);
}, false);
</script>
安卓平台差异化处理
/* Android 平台 Post Message 消息监听 Hook */
window.Android_handleMessage = message => {
// Android 使用 Base64 编码格式,需要先解码
let data = decodeURIComponent(escape(window.atob(message)));
};
安全问题
- 如果你不希望从其他网站接收 message,请不要为 message 事件添加任何事件监听器。
- 如果你确实希望从其他网站接收message,请始终使用 origin 和 source 属性验证发件人的身份。
- 当你使用 postMessage 将数据发送到其他窗口时,始终指定精确的目标 origin,而不是 *。
2.WebSocket
WebSocket—— 它是浏览器与服务器的全双工实时通信协议,核心解决 HTTP 协议 “请求 - 响应” 模式的局限性,支持服务器主动向客户端推送数据,是实时通信场景的首选方案,下面从核心概念、用法、区别等维度全面讲解:
核心概念
- 全双工通信:客户端与服务器建立连接后,双方可同时发送数据(双向实时通信),无需像 HTTP 那样每次通信都发起请求。
- 持久连接:连接建立后持续保持,直到一方主动关闭,避免 HTTP 频繁握手的开销。
- 协议标识:URL 协议以 ws://(非加密)或 wss://(加密,类似 HTTPS)开头,默认端口分别为 80 和 443。
- 核心特性:
- 无同源限制(客户端可跨域连接服务器,需服务器配置允许);
- 数据传输格式灵活(文本、二进制数据均可);
- 低延迟、低开销(连接建立后,数据传输仅包含少量帧头)。
- 生命周期:建立连接(握手)→ 数据传输 → 关闭连接(主动 / 被动)。
基本用法
1.前端(浏览器)WebSocket 核心 API
浏览器原生支持 WebSocket 对象,无需依赖第三方库,核心方法与事件如下
// 前端:WebSocket 客户端实现
let ws = null;
// 1. 建立连接(ws:// 非加密,wss:// 加密)
function initWebSocket() {
// 检测浏览器支持(所有现代浏览器均支持,IE 需 10+)
if (!window.WebSocket) {
console.warn('当前浏览器不支持 WebSocket');
return;
}
// 连接服务器(替换为你的 WebSocket 服务地址)
ws = new WebSocket('ws://localhost:8080/ws');
// 2. 连接建立成功事件
ws.onopen = function() {
console.log('WebSocket 连接已建立');
// 连接成功后发送初始消息
ws.send(JSON.stringify({
type: 'login',
data: { username: '前端用户' }
}));
};
// 3. 接收服务器消息事件
ws.onmessage = function(event) {
// event.data 为服务器发送的数据(文本/二进制)
const message = JSON.parse(event.data);
console.log('收到服务器消息:', message);
// 处理业务逻辑(如渲染聊天消息、更新行情)
handleServerMessage(message);
};
// 4. 连接关闭事件
ws.onclose = function(event) {
console.log(`WebSocket 连接关闭(状态码:${event.code},原因:${event.reason}`);
// 自动重连(可选,提升稳定性)
setTimeout(initWebSocket, 3000);
};
// 5. 连接错误事件
ws.onerror = function(error) {
console.error('WebSocket 错误:', error);
};
}
// 发送消息到服务器
function sendMessageToServer(data) {
if (ws && ws.readyState === WebSocket.OPEN) {
// 发送数据(支持字符串、Blob、ArrayBuffer)
ws.send(JSON.stringify(data));
} else {
console.warn('WebSocket 连接未建立或已关闭');
}
}
// 处理服务器消息
function handleServerMessage(message) {
switch (message.type) {
case 'login-success':
alert(`欢迎登录,当前在线人数:${message.data.onlineCount}`);
break;
case 'chat-message':
// 渲染聊天消息到页面
const chatDom = document.createElement('div');
chatDom.textContent = `${message.data.username}:${message.data.content}`;
document.getElementById('chat-container').appendChild(chatDom);
break;
case 'system-notice':
alert(`系统通知:${message.data.content}`);
break;
}
}
// 关闭 WebSocket 连接
function closeWebSocket() {
if (ws) {
ws.close(1000, '用户主动关闭'); // 1000 为正常关闭状态码
}
}
// 页面加载时初始化连接
window.addEventListener('load', initWebSocket);
// 页面卸载时关闭连接
window.addEventListener('beforeunload', closeWebSocket);
2. 后端(Node.js)WebSocket 服务实现(使用 ws 库)
后端需搭建 WebSocket 服务,这里以 Node.js 为例(需安装 ws 库:npm install ws):
// 后端:Node.js WebSocket 服务(server.js)
const WebSocket = require('ws');
const http = require('http');
// 1. 创建 HTTP 服务器(WebSocket 基于 HTTP 握手)
const server = http.createServer((req, res) => {
res.writeHead(200);
res.end('WebSocket 服务运行中');
});
// 2. 创建 WebSocket 服务(绑定到 HTTP 服务器)
const wss = new WebSocket.Server({ server });
// 存储所有连接的客户端
let clients = new Set();
// 在线人数
let onlineCount = 0;
// 3. 监听客户端连接
wss.on('connection', (ws) => {
console.log('新客户端连接');
clients.add(ws);
onlineCount++;
// 4. 监听客户端发送的消息
ws.on('message', (message) => {
console.log('收到客户端消息:', message.toString());
const data = JSON.parse(message.toString());
// 处理不同类型的消息
switch (data.type) {
case 'login':
// 广播登录通知给所有客户端
broadcastMessage({
type: 'login-success',
data: { username: data.data.username, onlineCount }
});
break;
case 'chat-message':
// 广播聊天消息给所有客户端
broadcastMessage({
type: 'chat-message',
data: { username: data.data.username, content: data.data.content }
});
break;
}
});
// 5. 监听客户端断开连接
ws.on('close', () => {
console.log('客户端断开连接');
clients.delete(ws);
onlineCount--;
// 广播在线人数更新
broadcastMessage({
type: 'system-notice',
data: { content: `有用户离开,当前在线人数:${onlineCount}` }
});
});
// 6. 监听连接错误
ws.on('error', (error) => {
console.error('WebSocket 服务错误:', error);
});
});
// 工具函数:广播消息给所有客户端
function broadcastMessage(message) {
const jsonMessage = JSON.stringify(message);
clients.forEach((client) => {
// 确保客户端连接正常
if (client.readyState === WebSocket.OPEN) {
client.send(jsonMessage);
}
});
}
// 启动服务器(端口 8080)
server.listen(8080, () => {
console.log('WebSocket 服务启动成功,地址:ws://localhost:8080/ws');
});
3. 前端 HTML 结构(聊天示例)
<!DOCTYPE html>
<html>
<body>
<h3>WebSocket 实时聊天示例</h3>
<div id="chat-container" style="height: 300px; border: 1px solid #ccc; padding: 10px; overflow-y: auto;"></div>
<div style="margin-top: 10px;">
<input type="text" id="message-input" placeholder="输入消息..." style="width: 300px;">
<button onclick="sendChatMessage()">发送</button>
</div>
<script>
// 辅助函数:发送聊天消息
function sendChatMessage() {
const content = document.getElementById('message-input').value.trim();
if (!content) return;
sendMessageToServer({
type: 'chat-message',
data: { username: '前端用户', content }
});
// 清空输入框
document.getElementById('message-input').value = '';
}
// 绑定回车发送
document.getElementById('message-input').addEventListener('keypress', (e) => {
if (e.key === 'Enter') sendChatMessage();
});
// 引入前面的 WebSocket 核心逻辑
// ...(此处省略 initWebSocket 等函数,需与前面代码合并)
</script>
</body>
</html>
核心API
前端 WebSocket 对象核心属性
| 属性 | 说明 |
|---|---|
| readyState | 连接状态:0(CONNECTING,连接中)、1(OPEN,已连接)、2(CLOSING,关闭中)、3(CLOSED,已关闭) |
| bufferedAmount | 已发送但未被服务器确认的字节数(用于流量控制) |
| protocol | 连接使用的子协议(建立连接时协商) |
核心方法
- new WebSocket(url[, protocols]):创建 WebSocket 实例,url 为服务端地址,protocols 可选(指定子协议)。
- ws.send(data):发送数据到服务器,data 支持字符串、Blob、ArrayBuffer、ArrayBufferView。
- ws.close(code[, reason]):关闭连接,code 为状态码(1000 表示正常关闭),reason 为关闭原因(字符串)。
核心事件
- onopen:连接建立成功时触发。
- onmessage:收到服务器消息时触发,event.data 为消息内容。
- onclose:连接关闭时触发,event.code 为状态码,event.reason 为关闭原因。
- onerror:连接出错时触发(如网络中断、服务器异常)。
典型应用场景
- 实时聊天系统:如在线客服、社交聊天(微信网页版、Discord)。
- 实时数据推送:如股票 / 加密货币行情、体育赛事比分、实时监控数据。
- 协同编辑工具:如在线文档(Google Docs)、代码协作平台(VS Code Live Share)。
- 实时通知:如系统公告、订单状态更新、消息提醒。
- 多人在线游戏:如实时对战游戏(如贪吃蛇联机版)的状态同步。
注意事项与踩坑指南
- 连接状态判断:发送消息前需检查 ws.readyState === WebSocket.OPEN,避免在连接中 / 已关闭状态发送。
- 数据序列化:建议使用 JSON 格式传输复杂数据(需 JSON.stringify/JSON.parse),二进制数据使用 Blob/ArrayBuffer。
- 状态码规范:关闭连接时使用标准状态码(1000 正常关闭、1001 客户端离开、1002 协议错误、1003 不支持的数据类型)。
- 服务器压力:WebSocket 是持久连接,需服务器支持高并发(如使用 Node.js ws 库、Java Netty、Go WebSocket 库),避免单线程阻塞。
- 兼容性:支持所有现代浏览器(Chrome、Firefox、Edge、Safari),IE 需 10+ 版本(部分 API 可能有差异)。
- 加密传输:生产环境建议使用 wss://(WebSocket Secure),避免数据被窃听或篡改(类似 HTTPS)。
- 防火墙 / 代理限制:部分网络环境(如公司内网)可能拦截 WebSocket 端口(80/443 通常放行),需确保服务器使用标准端口。
方案选择建议
| 场景需求 | 推荐方案 |
|---|---|
| 实时双向通信(需服务器参与) | WebSocket |
| 前端跨页面 / 线程通信(无服务器) | Broadcast Channel/SharedWorker |
| 离线应用 + 后台同步 | Service Worker + WebSocket(实时同步) |
| 单页面复杂计算(不阻塞 UI) | Web Worker |
| 跨域前端通信(无服务器) | window.postMessage |
| 普通非实时数据请求 | HTTP(AJAX/Fetch) |