前端跨页面通信方式

文章类型:实战

发布者:hp

发布时间:2026-07-11

Web应用中,经常需要在不同的浏览器标签页或窗口之间进行数据通信。本文将介绍所有主流的跨页面通信方式,包括原理、适用场景和完整代码示例。

通信方式概览

方式同源限制实时性数据大小兼容性适用场景
BroadcastChannel同源实时无限制现代浏览器同源多标签通信
LocalStorage + storage事件同源实时5-10MB所有浏览器同源多标签通信
SharedWorker同源实时无限制现代浏览器复杂状态共享
postMessage跨域实时无限制所有浏览器跨域iframe通信
WebSocket跨域实时无限制现代浏览器服务器中转
IndexedDB同源轮询GB级现代浏览器大数据共享
Cookie同域轮询4KB所有浏览器简单数据共享
Service Worker同源实时无限制现代浏览器离线通信

方法一:BroadcastChannel API(最推荐)


简介

BroadcastChannel是专门为跨标签页通信设计的API,使用简单、性能好、实时性强。

优点

  • API简单直观
  • 实时通信
  • 无数据大小限制
  • 性能好
  • 自动处理多标签
  • 缺点
  • 不支持IE
  • 仅限同源
  • 无法持久化

基础使用

// 页面A - 创建频道并发送消息
const channel = new BroadcastChannel('my_channel');

// 发送消息
channel.postMessage('Hello from Page A');
channel.postMessage({ type: 'user_login', userId: 123 });

// 监听消息
channel.onmessage = (event) => {
    console.log('收到消息:', event.data);
};

// 关闭频道
channel.close();


// 页面B - 监听同一频道
const channel = new BroadcastChannel('my_channel');

channel.onmessage = (event) => {
    console.log('Page B收到消息:', event.data);
    
    // 可以回复消息
    channel.postMessage('Message received by Page B');
};

实战案例:多标签同步登录状态

// auth.js - 认证模块
class AuthManager {
    constructor() {
        this.channel = new BroadcastChannel('auth_channel');
        this.setupListener();
    }
    
    // 监听其他标签页的认证事件
    setupListener() {
        this.channel.onmessage = (event) => {
            const { type, data } = event.data;
            
            switch(type) {
                case 'LOGIN':
                    this.handleRemoteLogin(data);
                    break;
                case 'LOGOUT':
                    this.handleRemoteLogout();
                    break;
                case 'TOKEN_REFRESH':
                    this.handleTokenRefresh(data);
                    break;
            }
        };
    }
    
    // 登录
    login(user, token) {
        // 保存到本地
        localStorage.setItem('user', JSON.stringify(user));
        localStorage.setItem('token', token);
        
        // 通知其他标签页
        this.channel.postMessage({
            type: 'LOGIN',
            data: { user, token }
        });
        
        console.log('登录成功,已通知其他标签页');
    }
    
    // 登出
    logout() {
        localStorage.removeItem('user');
        localStorage.removeItem('token');
        
        // 通知其他标签页
        this.channel.postMessage({ type: 'LOGOUT' });
        
        // 跳转到登录页
        window.location.href = '/login';
    }
    
    // 处理远程登录
    handleRemoteLogin(data) {
        console.log('其他标签页已登录,同步状态');
        localStorage.setItem('user', JSON.stringify(data.user));
        localStorage.setItem('token', data.token);
        
        // 更新UI
        this.updateUI(data.user);
    }
    
    // 处理远程登出
    handleRemoteLogout() {
        console.log('其他标签页已登出,同步登出');
        localStorage.removeItem('user');
        localStorage.removeItem('token');
        window.location.href = '/login';
    }
    
    // 刷新Token
    refreshToken(newToken) {
        localStorage.setItem('token', newToken);
        this.channel.postMessage({
            type: 'TOKEN_REFRESH',
            data: { token: newToken }
        });
    }
    
    handleTokenRefresh(data) {
        localStorage.setItem('token', data.token);
        console.log('Token已同步刷新');
    }
    
    updateUI(user) {
        // 更新页面UI
        document.getElementById('username').textContent = user.name;
        document.getElementById('login-btn').style.display = 'none';
        document.getElementById('logout-btn').style.display = 'block';
    }
}

// 使用
const authManager = new AuthManager();

// 登录按钮
document.getElementById('login-btn').addEventListener('click', () => {
    // 假设登录成功
    const user = { id: 1, name: '张三', email: 'zhangsan@example.com' };
    const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...';
    authManager.login(user, token);
});

// 登出按钮
document.getElementById('logout-btn').addEventListener('click', () => {
    authManager.logout();
});

实战案例:购物车同步

// cart.js - 购物车同步
class CartSync {
    constructor() {
        this.channel = new BroadcastChannel('cart_channel');
        this.cart = this.loadCart();
        this.setupListener();
    }
    
    loadCart() {
        const cart = localStorage.getItem('cart');
        return cart ? JSON.parse(cart) : [];
    }
    
    setupListener() {
        this.channel.onmessage = (event) => {
            const { action, data } = event.data;
            
            switch(action) {
                case 'ADD_ITEM':
                    this.handleRemoteAdd(data);
                    break;
                case 'REMOVE_ITEM':
                    this.handleRemoteRemove(data);
                    break;
                case 'UPDATE_QUANTITY':
                    this.handleRemoteUpdate(data);
                    break;
                case 'CLEAR_CART':
                    this.handleRemoteClear();
                    break;
            }
        };
    }
    
    // 添加商品
    addItem(product, quantity = 1) {
        const existingItem = this.cart.find(item => item.id === product.id);
        
        if (existingItem) {
            existingItem.quantity += quantity;
        } else {
            this.cart.push({ ...product, quantity });
        }
        
        this.saveCart();
        this.notifyOtherTabs('ADD_ITEM', { product, quantity });
        this.updateUI();
    }
    
    // 移除商品
    removeItem(productId) {
        this.cart = this.cart.filter(item => item.id !== productId);
        this.saveCart();
        this.notifyOtherTabs('REMOVE_ITEM', { productId });
        this.updateUI();
    }
    
    // 更新数量
    updateQuantity(productId, quantity) {
        const item = this.cart.find(item => item.id === productId);
        if (item) {
            item.quantity = quantity;
            this.saveCart();
            this.notifyOtherTabs('UPDATE_QUANTITY', { productId, quantity });
            this.updateUI();
        }
    }
    
    // 清空购物车
    clearCart() {
        this.cart = [];
        this.saveCart();
        this.notifyOtherTabs('CLEAR_CART', {});
        this.updateUI();
    }
    
    // 通知其他标签页
    notifyOtherTabs(action, data) {
        this.channel.postMessage({ action, data });
    }
    
    // 处理远程操作
    handleRemoteAdd(data) {
        console.log('其他标签页添加了商品');
        this.cart = this.loadCart();
        this.updateUI();
    }
    
    handleRemoteRemove(data) {
        console.log('其他标签页移除了商品');
        this.cart = this.loadCart();
        this.updateUI();
    }
    
    handleRemoteUpdate(data) {
        console.log('其他标签页更新了数量');
        this.cart = this.loadCart();
        this.updateUI();
    }
    
    handleRemoteClear() {
        console.log('其他标签页清空了购物车');
        this.cart = [];
        this.updateUI();
    }
    
    saveCart() {
        localStorage.setItem('cart', JSON.stringify(this.cart));
    }
    
    updateUI() {
        // 更新购物车图标数量
        const totalItems = this.cart.reduce((sum, item) => sum + item.quantity, 0);
        document.getElementById('cart-count').textContent = totalItems;
        
        // 更新购物车列表
        this.renderCartList();
    }
    
    renderCartList() {
        const cartElement = document.getElementById('cart-list');
        cartElement.innerHTML = this.cart.map(item => `
            
                ${item.name}
                数量: ${item.quantity}
                价格: ${item.price * item.quantity}元
                删除
            
        `).join('');
    }
}

// 使用
const cartSync = new CartSync();

兼容性检测

// 检测浏览器是否支持
if ('BroadcastChannel' in window) {
    const channel = new BroadcastChannel('my_channel');
    // 使用BroadcastChannel
} else {
    console.log('浏览器不支持BroadcastChannel,使用替代方案');
    // 使用LocalStorage + storage事件
}

方法二:LocalStorage + storage事件


简介

利用localStorage的storage事件实现跨标签页通信,兼容性最好的方案。

优点

  • 兼容性极好(IE8+)
  • 实时通信
  • 实现简单
  • 数据持久化
  • 缺点
  • 只能传字符串
  • 容量限制5-10MB
  • 同步API可能阻塞
  • 当前标签页不触发storage事件

工作原理

// 当localStorage发生变化时,其他标签页会触发storage事件
// 注意:当前标签页不会触发自己的storage事件

// 页面A - 发送消息
localStorage.setItem('message', JSON.stringify({
    type: 'notification',
    content: 'Hello',
    timestamp: Date.now()
}));

// 页面B - 监听消息
window.addEventListener('storage', (event) => {
    if (event.key === 'message') {
        const message = JSON.parse(event.newValue);
        console.log('收到消息:', message);
    }
});

封装通信工具类

// StorageChannel.js - 基于localStorage的通信频道
class StorageChannel {
    constructor(channelName) {
        this.channelName = channelName;
        this.listeners = [];
        this.init();
    }
    
    init() {
        window.addEventListener('storage', (event) => {
            if (event.key === this.channelName && event.newValue) {
                try {
                    const data = JSON.parse(event.newValue);
                    this.listeners.forEach(listener => listener(data));
                } catch (e) {
                    console.error('解析消息失败:', e);
                }
            }
        });
    }
    
    // 发送消息
    postMessage(data) {
        const message = {
            data: data,
            timestamp: Date.now(),
            sender: this.getPageId()
        };
        localStorage.setItem(this.channelName, JSON.stringify(message));
        
        // 立即删除,避免存储空间占用
        // 同时确保每次发送都会触发storage事件
        setTimeout(() => {
            localStorage.removeItem(this.channelName);
        }, 50);
    }
    
    // 监听消息
    onmessage(callback) {
        this.listeners.push(callback);
    }
    
    // 移除监听
    removeListener(callback) {
        this.listeners = this.listeners.filter(l => l !== callback);
    }
    
    // 生成页面ID
    getPageId() {
        if (!this._pageId) {
            this._pageId = Math.random().toString(36).substr(2, 9);
        }
        return this._pageId;
    }
    
    // 关闭频道
    close() {
        this.listeners = [];
    }
}

// 使用示例
const channel = new StorageChannel('my_channel');

// 发送消息
channel.postMessage({ type: 'hello', content: 'Hello World' });

// 监听消息
channel.onmessage((data) => {
    console.log('收到消息:', data);
});

实战案例:主题切换同步

// theme.js - 主题同步
class ThemeSync {
    constructor() {
        this.channel = new StorageChannel('theme_channel');
        this.currentTheme = this.loadTheme();
        this.setupListener();
        this.applyTheme(this.currentTheme);
    }
    
    loadTheme() {
        return localStorage.getItem('theme') || 'light';
    }
    
    setupListener() {
        this.channel.onmessage((message) => {
            if (message.type === 'THEME_CHANGE') {
                this.applyTheme(message.theme);
            }
        });
    }
    
    // 切换主题
    switchTheme(theme) {
        this.currentTheme = theme;
        localStorage.setItem('theme', theme);
        this.applyTheme(theme);
        
        // 通知其他标签页
        this.channel.postMessage({
            type: 'THEME_CHANGE',
            theme: theme
        });
    }
    
    // 应用主题
    applyTheme(theme) {
        document.body.className = `theme-${theme}`;
        console.log(`已切换到${theme}主题`);
    }
}

// 使用
const themeSync = new ThemeSync();

// 主题切换按钮
document.getElementById('theme-toggle').addEventListener('click', () => {
    const newTheme = themeSync.currentTheme === 'light' ? 'dark' : 'light';
    themeSync.switchTheme(newTheme);
});

实战案例:通知中心

// notification.js - 跨标签页通知
class NotificationSync {
    constructor() {
        this.channel = new StorageChannel('notification_channel');
        this.setupListener();
    }
    
    setupListener() {
        this.channel.onmessage((message) => {
            if (message.type === 'NEW_NOTIFICATION') {
                this.showNotification(message.data);
            }
        });
    }
    
    // 发送通知到所有标签页
    broadcast(notification) {
        this.channel.postMessage({
            type: 'NEW_NOTIFICATION',
            data: notification
        });
        
        // 也在当前页面显示
        this.showNotification(notification);
    }
    
    // 显示通知
    showNotification(notification) {
        // 浏览器通知
        if ('Notification' in window && Notification.permission === 'granted') {
            new Notification(notification.title, {
                body: notification.body,
                icon: notification.icon
            });
        }
        
        // 页面内通知
        const notificationElement = document.createElement('div');
        notificationElement.className = 'notification';
        notificationElement.innerHTML = `
            ${notification.title}
            ${notification.body}
        `;
        document.body.appendChild(notificationElement);
        
        // 3秒后自动消失
        setTimeout(() => {
            notificationElement.remove();
        }, 3000);
    }
}

// 使用
const notificationSync = new NotificationSync();

// 收到新消息时
function onNewMessage(message) {
    notificationSync.broadcast({
        title: '新消息',
        body: `来自${message.sender}: ${message.content}`,
        icon: '/icon.png'
    });
}

方法三:SharedWorker


简介

SharedWorker是运行在后台的脚本,可以被多个页面共享,适合复杂的状态管理和通信。

优点

  • 独立线程,不阻塞UI
  • 可以维护共享状态
  • 支持复杂逻辑
  • 实时双向通信
  • 缺点
  • 不支持Safari
  • 调试困难
  • 学习曲线陡

基础使用

// shared-worker.js - Worker脚本
const connections = [];

self.addEventListener('connect', (event) => {
    const port = event.ports[0];
    connections.push(port);
    
    console.log('新连接,当前连接数:', connections.length);
    
    port.onmessage = (e) => {
        console.log('收到消息:', e.data);
        
        // 广播给所有连接的页面
        connections.forEach(p => {
            if (p !== port) {  // 不发给自己
                p.postMessage(e.data);
            }
        });
    };
    
    port.start();
});


// 页面A - 连接Worker
const worker = new SharedWorker('shared-worker.js');

worker.port.onmessage = (event) => {
    console.log('收到消息:', event.data);
};

worker.port.postMessage('Hello from Page A');


// 页面B - 连接同一个Worker
const worker = new SharedWorker('shared-worker.js');

worker.port.onmessage = (event) => {
    console.log('收到消息:', event.data);
};

worker.port.postMessage('Hello from Page B');

实战案例:在线用户列表

// online-users-worker.js
let onlineUsers = new Set();

self.addEventListener('connect', (event) => {
    const port = event.ports[0];
    let userId = null;
    
    port.onmessage = (e) => {
        const { type, data } = e.data;
        
        switch(type) {
            case 'USER_ONLINE':
                userId = data.userId;
                onlineUsers.add(userId);
                broadcastUserList();
                break;
                
            case 'USER_OFFLINE':
                onlineUsers.delete(data.userId);
                broadcastUserList();
                break;
                
            case 'GET_USERS':
                port.postMessage({
                    type: 'USER_LIST',
                    users: Array.from(onlineUsers)
                });
                break;
        }
    };
    
    // 连接断开时移除用户
    port.addEventListener('close', () => {
        if (userId) {
            onlineUsers.delete(userId);
            broadcastUserList();
        }
    });
    
    port.start();
});

function broadcastUserList() {
    const message = {
        type: 'USER_LIST',
        users: Array.from(onlineUsers)
    };
    
    // 这里需要维护port列表来广播
    // 简化示例,实际需要存储所有port
}


// 页面使用
class OnlineUserManager {
    constructor(userId) {
        this.userId = userId;
        this.worker = new SharedWorker('online-users-worker.js');
        this.setupListener();
        this.goOnline();
    }
    
    setupListener() {
        this.worker.port.onmessage = (event) => {
            const { type, users } = event.data;
            
            if (type === 'USER_LIST') {
                this.updateUserList(users);
            }
        };
    }
    
    goOnline() {
        this.worker.port.postMessage({
            type: 'USER_ONLINE',
            data: { userId: this.userId }
        });
    }
    
    goOffline() {
        this.worker.port.postMessage({
            type: 'USER_OFFLINE',
            data: { userId: this.userId }
        });
    }
    
    updateUserList(users) {
        console.log('在线用户:', users);
        const userListEl = document.getElementById('online-users');
        userListEl.innerHTML = users.map(id => 
            `用户${id}`
        ).join('');
    }
}

// 使用
const userManager = new OnlineUserManager(123);

// 页面关闭前
window.addEventListener('beforeunload', () => {
    userManager.goOffline();
});

方法四:postMessage(跨域通信)


简介

window.postMessage可以安全地实现跨域通信,主要用于iframe和window.open打开的窗口。

优点

  • 支持跨域
  • 安全可控
  • 兼容性好
  • 双向通信
  • 缺点
  • 需要窗口引用
  • 仅限父子窗口
  • 需要验证来源

iframe通信

// 父页面
const iframe = document.getElementById('myIframe');

// 发送消息给iframe
iframe.contentWindow.postMessage({
    type: 'INIT',
    data: { userId: 123 }
}, 'https://child-domain.com');

// 监听iframe的消息
window.addEventListener('message', (event) => {
    // 验证来源
    if (event.origin !== 'https://child-domain.com') {
        return;
    }
    
    console.log('收到iframe消息:', event.data);
    
    if (event.data.type === 'REQUEST_DATA') {
        // 响应iframe的请求
        event.source.postMessage({
            type: 'RESPONSE_DATA',
            data: { name: '张三' }
        }, event.origin);
    }
});


// iframe页面(子页面)
// 发送消息给父页面
window.parent.postMessage({
    type: 'READY',
    data: { loaded: true }
}, 'https://parent-domain.com');

// 监听父页面的消息
window.addEventListener('message', (event) => {
    // 验证来源
    if (event.origin !== 'https://parent-domain.com') {
        return;
    }
    
    console.log('收到父页面消息:', event.data);
    
    if (event.data.type === 'INIT') {
        const userId = event.data.data.userId;
        console.log('用户ID:', userId);
    }
});

window.open通信

// 父窗口
const childWindow = window.open('https://example.com/child.html', '_blank');

// 等待子窗口加载完成
setTimeout(() => {
    childWindow.postMessage({
        type: 'GREETING',
        message: 'Hello Child'
    }, 'https://example.com');
}, 1000);

// 监听子窗口消息
window.addEventListener('message', (event) => {
    if (event.origin !== 'https://example.com') return;
    console.log('子窗口说:', event.data);
});


// 子窗口
// 通知父窗口已加载
window.opener.postMessage({
    type: 'LOADED',
    message: 'Child window loaded'
}, '*');

// 监听父窗口消息
window.addEventListener('message', (event) => {
    console.log('父窗口说:', event.data);
});

实战案例:跨域登录

// 主站点 - 嵌入登录iframe
function setupLoginIframe() {
    const iframe = document.getElementById('login-iframe');
    
    window.addEventListener('message', (event) => {
        // 验证来源
        if (event.origin !== 'https://auth.example.com') {
            return;
        }
        
        const { type, data } = event.data;
        
        switch(type) {
            case 'LOGIN_SUCCESS':
                handleLoginSuccess(data.token, data.user);
                break;
            case 'LOGIN_FAILED':
                handleLoginFailed(data.error);
                break;
        }
    });
}

function handleLoginSuccess(token, user) {
    localStorage.setItem('token', token);
    localStorage.setItem('user', JSON.stringify(user));
    
    // 关闭登录iframe
    document.getElementById('login-iframe').style.display = 'none';
    
    // 更新UI
    console.log('登录成功:', user);
}


// 登录站点(iframe内)
function submitLogin(username, password) {
    // 执行登录逻辑
    fetch('/api/login', {
        method: 'POST',
        body: JSON.stringify({ username, password })
    })
    .then(res => res.json())
    .then(data => {
        if (data.success) {
            // 通知父页面登录成功
            window.parent.postMessage({
                type: 'LOGIN_SUCCESS',
                data: {
                    token: data.token,
                    user: data.user
                }
            }, 'https://main.example.com');
        } else {
            window.parent.postMessage({
                type: 'LOGIN_FAILED',
                data: { error: data.message }
            }, 'https://main.example.com');
        }
    });
}

方法五:WebSocket(服务器中转)


简介

通过WebSocket服务器中转消息,适合需要服务器参与的场景,如实时聊天、协同编辑等。

客户端实现

// WebSocket通信封装
class WebSocketChannel {
    constructor(url, userId) {
        this.url = url;
        this.userId = userId;
        this.ws = null;
        this.listeners = [];
        this.connect();
    }
    
    connect() {
        this.ws = new WebSocket(this.url);
        
        this.ws.onopen = () => {
            console.log('WebSocket连接成功');
            // 注册用户
            this.send({
                type: 'REGISTER',
                userId: this.userId
            });
        };
        
        this.ws.onmessage = (event) => {
            const message = JSON.parse(event.data);
            this.listeners.forEach(listener => listener(message));
        };
        
        this.ws.onerror = (error) => {
            console.error('WebSocket错误:', error);
        };
        
        this.ws.onclose = () => {
            console.log('WebSocket连接关闭,3秒后重连');
            setTimeout(() => this.connect(), 3000);
        };
    }
    
    send(message) {
        if (this.ws.readyState === WebSocket.OPEN) {
            this.ws.send(JSON.stringify(message));
        }
    }
    
    onMessage(callback) {
        this.listeners.push(callback);
    }
    
    broadcast(data) {
        this.send({
            type: 'BROADCAST',
            data: data,
            sender: this.userId
        });
    }
    
    close() {
        this.ws.close();
    }
}

// 使用
const channel = new WebSocketChannel('ws://localhost:8080', 'user123');

channel.onMessage((message) => {
    console.log('收到消息:', message);
});

channel.broadcast({ text: 'Hello everyone!' });

服务器端实现(Node.js)

// server.js
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });

// 存储所有连接的客户端
const clients = new Map();

wss.on('connection', (ws) => {
    let userId = null;
    
    ws.on('message', (data) => {
        const message = JSON.parse(data);
        
        switch(message.type) {
            case 'REGISTER':
                userId = message.userId;
                clients.set(userId, ws);
                console.log(`用户${userId}已连接,当前在线${clients.size}人`);
                break;
                
            case 'BROADCAST':
                // 广播给所有客户端
                broadcast(message, userId);
                break;
                
            case 'PRIVATE':
                // 私聊
                sendPrivate(message.to, message.data);
                break;
        }
    });
    
    ws.on('close', () => {
        if (userId) {
            clients.delete(userId);
            console.log(`用户${userId}已断开`);
        }
    });
});

// 广播消息
function broadcast(message, senderUserId) {
    clients.forEach((client, userId) => {
        if (client.readyState === WebSocket.OPEN && userId !== senderUserId) {
            client.send(JSON.stringify(message));
        }
    });
}

// 发送私聊消息
function sendPrivate(userId, data) {
    const client = clients.get(userId);
    if (client && client.readyState === WebSocket.OPEN) {
        client.send(JSON.stringify({
            type: 'PRIVATE',
            data: data
        }));
    }
}

console.log('WebSocket服务器运行在端口8080');

实战案例:协同编辑

// collaborative-editor.js
class CollaborativeEditor {
    constructor(docId, userId) {
        this.docId = docId;
        this.userId = userId;
        this.channel = new WebSocketChannel('ws://localhost:8080', userId);
        this.editor = document.getElementById('editor');
        this.setupListener();
        this.bindEvents();
    }
    
    setupListener() {
        this.channel.onMessage((message) => {
            if (message.type === 'TEXT_CHANGE' && message.docId === this.docId) {
                this.applyRemoteChange(message.data);
            }
        });
    }
    
    bindEvents() {
        let timeout;
        this.editor.addEventListener('input', () => {
            clearTimeout(timeout);
            timeout = setTimeout(() => {
                this.sendChange();
            }, 300);
        });
    }
    
    sendChange() {
        const content = this.editor.value;
        this.channel.send({
            type: 'TEXT_CHANGE',
            docId: this.docId,
            data: {
                content: content,
                cursor: this.editor.selectionStart
            }
        });
    }
    
    applyRemoteChange(data) {
        const currentCursor = this.editor.selectionStart;
        this.editor.value = data.content;
        // 保持光标位置
        this.editor.setSelectionRange(currentCursor, currentCursor);
    }
}

// 使用
const editor = new CollaborativeEditor('doc123', 'user456');

方法六:其他通信方式


1. IndexedDB(大数据共享)

// 使用IndexedDB + 轮询
class IndexedDBChannel {
    constructor(dbName) {
        this.dbName = dbName;
        this.db = null;
        this.init();
    }
    
    async init() {
        return new Promise((resolve, reject) => {
            const request = indexedDB.open(this.dbName, 1);
            
            request.onerror = () => reject(request.error);
            request.onsuccess = () => {
                this.db = request.result;
                resolve();
            };
            
            request.onupgradeneeded = (event) => {
                const db = event.target.result;
                if (!db.objectStoreNames.contains('messages')) {
                    db.createObjectStore('messages', { keyPath: 'id', autoIncrement: true });
                }
            };
        });
    }
    
    async postMessage(data) {
        return new Promise((resolve, reject) => {
            const transaction = this.db.transaction(['messages'], 'readwrite');
            const store = transaction.objectStore('messages');
            const message = {
                data: data,
                timestamp: Date.now()
            };
            const request = store.add(message);
            request.onsuccess = () => resolve();
            request.onerror = () => reject(request.error);
        });
    }
    
    async getMessages(since) {
        return new Promise((resolve, reject) => {
            const transaction = this.db.transaction(['messages'], 'readonly');
            const store = transaction.objectStore('messages');
            const request = store.getAll();
            
            request.onsuccess = () => {
                const messages = request.result.filter(m => m.timestamp > since);
                resolve(messages);
            };
            request.onerror = () => reject(request.error);
        });
    }
    
    startPolling(callback) {
        let lastCheck = Date.now();
        setInterval(async () => {
            const messages = await this.getMessages(lastCheck);
            messages.forEach(msg => callback(msg.data));
            lastCheck = Date.now();
        }, 1000);
    }
}

2. Cookie + 轮询

// 简单的Cookie通信(不推荐,仅作了解)
class CookieChannel {
    constructor(channelName) {
        this.channelName = channelName;
    }
    
    postMessage(data) {
        const message = JSON.stringify({
            data: data,
            timestamp: Date.now()
        });
        document.cookie = `${this.channelName}=${encodeURIComponent(message)}; path=/`;
    }
    
    getMessage() {
        const cookies = document.cookie.split('; ');
        const cookie = cookies.find(c => c.startsWith(this.channelName + '='));
        if (cookie) {
            const value = decodeURIComponent(cookie.split('=')[1]);
            return JSON.parse(value);
        }
        return null;
    }
    
    startPolling(callback) {
        let lastTimestamp = 0;
        setInterval(() => {
            const message = this.getMessage();
            if (message && message.timestamp > lastTimestamp) {
                callback(message.data);
                lastTimestamp = message.timestamp;
            }
        }, 500);
    }
}

3. Service Worker

// sw.js - Service Worker
const clients = [];

self.addEventListener('message', (event) => {
    const { type, data } = event.data;
    
    if (type === 'BROADCAST') {
        // 发送给所有客户端
        self.clients.matchAll().then(clientList => {
            clientList.forEach(client => {
                client.postMessage({
                    type: 'MESSAGE',
                    data: data
                });
            });
        });
    }
});


// 页面中使用
navigator.serviceWorker.register('/sw.js');

navigator.serviceWorker.ready.then(registration => {
    // 发送消息
    registration.active.postMessage({
        type: 'BROADCAST',
        data: { text: 'Hello' }
    });
    
    // 接收消息
    navigator.serviceWorker.addEventListener('message', (event) => {
        console.log('收到消息:', event.data);
    });
});

方案选择建议


根据场景选择

同源多标签通信(推荐方案)

  • 首选:BroadcastChannel(现代浏览器)
  • 备选:LocalStorage + storage事件(兼容性最好)

复杂状态管理

  • 首选:SharedWorker
  • 备选:WebSocket

跨域通信

  • 父子窗口:postMessage
  • 任意页面:WebSocket

大数据共享

  • 首选:IndexedDB
  • 备选:WebSocket传输文件

实时性要求高

  • 首选:WebSocket
  • 备选:BroadcastChannel

兼容性处理


渐进增强方案

// 统一的通信接口
class CrossPageChannel {
    constructor(channelName) {
        this.channelName = channelName;
        this.channel = this.createChannel();
    }
    
    createChannel() {
        // 优先使用BroadcastChannel
        if ('BroadcastChannel' in window) {
            console.log('使用BroadcastChannel');
            return new BroadcastChannelAdapter(this.channelName);
        }
        // 降级到LocalStorage
        else {
            console.log('使用LocalStorage');
            return new StorageChannelAdapter(this.channelName);
        }
    }
    
    postMessage(data) {
        this.channel.postMessage(data);
    }
    
    onMessage(callback) {
        this.channel.onMessage(callback);
    }
    
    close() {
        this.channel.close();
    }
}

// BroadcastChannel适配器
class BroadcastChannelAdapter {
    constructor(channelName) {
        this.channel = new BroadcastChannel(channelName);
    }
    
    postMessage(data) {
        this.channel.postMessage(data);
    }
    
    onMessage(callback) {
        this.channel.onmessage = (event) => callback(event.data);
    }
    
    close() {
        this.channel.close();
    }
}

// LocalStorage适配器
class StorageChannelAdapter {
    constructor(channelName) {
        this.channelName = channelName;
        this.listeners = [];
        this.init();
    }
    
    init() {
        window.addEventListener('storage', (event) => {
            if (event.key === this.channelName && event.newValue) {
                const data = JSON.parse(event.newValue);
                this.listeners.forEach(cb => cb(data.data));
            }
        });
    }
    
    postMessage(data) {
        const message = {
            data: data,
            timestamp: Date.now()
        };
        localStorage.setItem(this.channelName, JSON.stringify(message));
        setTimeout(() => {
            localStorage.removeItem(this.channelName);
        }, 50);
    }
    
    onMessage(callback) {
        this.listeners.push(callback);
    }
    
    close() {
        this.listeners = [];
    }
}

// 使用统一接口
const channel = new CrossPageChannel('my_channel');
channel.postMessage({ type: 'hello', content: 'Hello World' });
channel.onMessage((data) => {
    console.log('收到消息:', data);
});

总结


核心要点

  1. BroadcastChannel是首选:API简单,性能好,实时性强
  2. LocalStorage兼容性最好:适合需要支持老浏览器的项目
  3. SharedWorker适合复杂场景:可维护共享状态
  4. postMessage用于跨域:iframe和弹窗通信
  5. WebSocket用于服务器参与:实时聊天、协同编辑等

最佳实践

  • 使用渐进增强,提供降级方案
  • 验证消息来源,防止XSS攻击
  • 合理控制通信频率,避免性能问题
  • 考虑数据大小限制
  • 处理好页面关闭时的清理工作

常见应用场景

  • 多标签登录状态同步
  • 购物车实时同步
  • 主题切换同步
  • 通知推送
  • 在线用户列表
  • 协同编辑
  • 实时聊天


评论
0条评论遵守法律,文明用语,共同建设文明评论区

暂无评论,快来发表第一条评论吧~