页面通信(进程通信的几种方式)

202 阅读22分钟
  • 同源:如果两个页面的协议,域名和端口都相同,则两个页面具有相同的源。
  • 同源策略:它是浏览器提供的一个安全功能,通俗的理解:浏览器规定,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>

关键特性

  1. 独立线程:SharedWorker 运行在单独的后台线程,不阻塞页面主线程(适合处理复杂计算、实时数据监听)。​
  2. 全局状态管理:可在线程中维护全局状态(如用户信息、配置数据),所有页面共享同一状态,无需重复请求后端。​
  3. 通信机制:​
  • 页面 ↔ SharedWorker:通过 port.postMessage 发送消息,port.onmessage 监听。
  • SharedWorker ↔ 所有页面:通过遍历连接端口 broadcastMessage 实现广播。
  1. 数据类型支持:与 Broadcast Channel 一致,支持基本类型、对象、数组、Date 等(结构化克隆算法),不支持函数、Symbol、循环引用。​
  2. 生命周期:​
  • 当所有页面断开连接(关闭标签页 / 刷新),SharedWorker 线程会被浏览器销毁。
  • 线程销毁后,全局状态丢失(如需持久化,需结合 localStorage/IndexedDB)。

常见应用场景

  1. 全局登录状态管理:一个标签页登录后,SharedWorker 广播状态,所有页面自动更新登录信息,无需后端查询。​
  2. 实时数据同步:如股票行情、聊天消息,通过 SharedWorker 监听 WebSocket 连接,所有页面共享实时数据(避免多页面重复建立 WebSocket)。​
  3. 多页面协同编辑:如在线文档,SharedWorker 维护文档最新内容,同步所有编辑页面的操作(如光标位置、修改内容)。​
  4. 在线人数统计:通过 connections 数组记录连接的页面数,实时广播在线人数。

注意事项与踩坑指南

  1. 必须通过服务器访问:本地文件 file:// 协议下,SharedWorker 可能因安全限制无法创建,需通过 http:// 协议(如本地服务器、Nginx)访问。​
  2. 调试方式:​
  • Chrome:F12 → More Tools → Application → Shared Workers,可查看运行的线程并调试。​
  • Firefox:F12 → Debugger → Workers,选择对应的 SharedWorker 进行调试。​
  1. 跨域限制:同域名(协议 + 域名 + 端口)是硬性要求,跨域页面无法连接同一 SharedWorker。​
  2. 内存泄漏:​
  • 页面关闭时,SharedWorker 会自动移除连接,但建议在页面 beforeunload 时主动关闭端口:
window.addEventListener('beforeunload', () => {
    worker.port.close(); // 关闭当前页面与线程的连接
});
  1. 浏览器兼容性:不支持 IE 浏览器,Edge 需 79+ 版本,如需兼容低版本浏览器,可降级为 Broadcast Channel 或 localStorage。​
  2. 线程重启:当所有页面断开连接后,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 事件,自动同步缓存的请求到服务器。

典型应用场景

  1. 离线 Web 应用:如文档编辑、新闻阅读类应用,离线时可访问缓存内容,联网后同步数据。​
  2. 推送通知:即使页面关闭,Service Worker 仍可接收服务器推送的通知(需配合 Push API)。​
  3. 跨页面状态同步:如登录状态、系统配置更新,通过 Service Worker 广播给所有页面。​
  4. 后台数据同步:离线时用户操作的数据(如表单、评论),联网后自动同步,提升用户体验。

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)