最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

JS中BroadcastChannel瀏覽器原生跨標簽頁通信

 更新時間:2026年03月04日 08:46:32   作者:大知閑閑i  
在現(xiàn)代Web應用開發(fā)中,跨標簽頁通信是一個常見需求,本文就來詳細的介紹一下JS中BroadcastChannel瀏覽器原生跨標簽頁通信,感興趣的可以了解一下

在現(xiàn)代Web應用開發(fā)中,跨標簽頁通信是一個常見需求。無論是實現(xiàn)多標簽頁間的數(shù)據(jù)同步、構建協(xié)作工具,還是簡單的消息廣播,開發(fā)者都需要一個可靠的通信方案。雖然過去我們有 localStorage、postMessage 等方案,但 BroadcastChannel API 提供了一個更優(yōu)雅、更專業(yè)的解決方案。

什么是 BroadcastChannel?

BroadcastChannel 是 HTML5 中引入的一個專門用于同源頁面間通信的 API。它允許同一源下的不同瀏覽上下文(如標簽頁、iframe、Web Worker)之間進行消息廣播。

核心特點

  • 同源限制:只能在相同協(xié)議、域名、端口的頁面間通信
  • 一對多通信:一條消息可以同時被所有監(jiān)聽者接收
  • 雙向通信:所有參與者既可以發(fā)送消息,也可以接收消息
  • 自動清理:頁面關閉后自動斷開連接

基礎用法

1. 創(chuàng)建或加入頻道

// 創(chuàng)建/加入名為 "chat_room" 的頻道
const channel = new BroadcastChannel('chat_room');
?
// 查看頻道名稱
console.log(channel.name); // 輸出: "chat_room"

2. 發(fā)送消息

// 發(fā)送字符串
channel.postMessage('Hello from Page 1');
?
// 發(fā)送對象
channel.postMessage({
  type: 'user_action',
  user: '張三',
  action: 'click',
  timestamp: Date.now()
});
?
// 支持大多數(shù)數(shù)據(jù)類型
channel.postMessage(['數(shù)組', '數(shù)據(jù)']);
channel.postMessage(new Blob(['文件內容']));
channel.postMessage(new Uint8Array([1, 2, 3]));

3. 接收消息

// 方式1:使用 onmessage
channel.onmessage = (event) => {
  console.log('收到消息:', event.data);
  console.log('消息來源:', event.origin);
  console.log('時間戳:', event.timeStamp);
};
?
// 方式2:使用 addEventListener
channel.addEventListener('message', (event) => {
  console.log('收到消息:', event.data);
});
?
// 錯誤處理
channel.onmessageerror = (error) => {
  console.error('消息處理錯誤:', error);
};

4. 關閉頻道

// 關閉頻道,不再接收消息
channel.close();

實際應用場景

場景1:主題同步

當用戶在一個標簽頁切換主題時,所有其他標簽頁自動同步:

// theme-sync.js
class ThemeSync {
  constructor() {
    this.channel = new BroadcastChannel('theme_sync');
    this.setupListener();
  }
  
  setupListener() {
    this.channel.onmessage = (event) => {
      if (event.data.type === 'theme_change') {
        this.applyTheme(event.data.theme);
      }
    };
  }
  
  changeTheme(theme) {
    this.applyTheme(theme);
    this.channel.postMessage({
      type: 'theme_change',
      theme: theme,
      from: this.getTabId()
    });
  }
  
  applyTheme(theme) {
    document.body.className = `theme-${theme}`;
    localStorage.setItem('preferred_theme', theme);
  }
  
  getTabId() {
    return sessionStorage.getItem('tab_id') || 
           Math.random().toString(36).substring(7);
  }
}
?
// 使用
const themeSync = new ThemeSync();
themeSync.changeTheme('dark');

場景2:實時聊天室

創(chuàng)建一個簡單的多標簽頁聊天室:

<!-- chat.html -->
<!DOCTYPE html>
<html>
<head>
    <title>BroadcastChannel 聊天室</title>
    <style>
        .chat-container { max-width: 600px; margin: 0 auto; padding: 20px; }
        .message-list { 
            height: 400px; 
            overflow-y: auto; 
            border: 1px solid #ccc; 
            padding: 10px;
            margin-bottom: 10px;
        }
        .message { margin: 5px 0; padding: 8px; background: #f0f0f0; border-radius: 5px; }
        .system { background: #e3f2fd; text-align: center; }
        .self { background: #e8f5e8; border-left: 3px solid #4caf50; }
        .input-area { display: flex; gap: 10px; }
        #messageInput { flex: 1; padding: 8px; }
        button { padding: 8px 15px; background: #4caf50; color: white; border: none; border-radius: 3px; cursor: pointer; }
    </style>
</head>
<body>
    <div class="chat-container">
        <h1>?? 跨標簽頁聊天室</h1>
        <div class="message-list" id="messageList"></div>
        <div class="input-area">
            <input type="text" id="messageInput" placeholder="輸入消息..." onkeypress="if(event.key==='Enter') sendMessage()">
            <button onclick="sendMessage()">發(fā)送</button>
            <button onclick="changeNickname()">修改昵稱</button>
        </div>
    </div>
?
    <script>
        // 聊天室邏輯
        const chatChannel = new BroadcastChannel('global_chat');
        const userId = Math.random().toString(36).substring(2, 10);
        let nickname = '用戶_' + userId.substring(0, 4);
        
        // 監(jiān)聽消息
        chatChannel.onmessage = (event) => {
            const { type, data, from, userId: msgUserId } = event.data;
            
            switch(type) {
                case 'message':
                    displayMessage(from, data, msgUserId === userId);
                    break;
                case 'join':
                    displaySystemMessage(`${from} 加入了聊天室`);
                    break;
                case 'leave':
                    displaySystemMessage(`${from} 離開了聊天室`);
                    break;
                case 'nickname_change':
                    displaySystemMessage(`${from} 改名為 ${data}`);
                    break;
            }
        };
        
        // 廣播加入消息
        chatChannel.postMessage({
            type: 'join',
            from: nickname,
            userId: userId,
            time: Date.now()
        });
        
        function sendMessage() {
            const input = document.getElementById('messageInput');
            const text = input.value.trim();
            
            if (text) {
                chatChannel.postMessage({
                    type: 'message',
                    from: nickname,
                    data: text,
                    userId: userId,
                    time: Date.now()
                });
                
                displayMessage(nickname, text, true);
                input.value = '';
            }
        }
        
        function changeNickname() {
            const newNickname = prompt('請輸入新昵稱:', nickname);
            if (newNickname && newNickname.trim() && newNickname !== nickname) {
                const oldNickname = nickname;
                nickname = newNickname.trim();
                
                chatChannel.postMessage({
                    type: 'nickname_change',
                    from: oldNickname,
                    data: nickname,
                    userId: userId,
                    time: Date.now()
                });
            }
        }
        
        function displayMessage(sender, text, isSelf = false) {
            const list = document.getElementById('messageList');
            const msgDiv = document.createElement('div');
            msgDiv.className = `message ${isSelf ? 'self' : ''}`;
            
            const time = new Date().toLocaleTimeString('zh-CN', { 
                hour: '2-digit', 
                minute: '2-digit' 
            });
            
            msgDiv.innerHTML = `<strong>${sender}${isSelf ? ' (我)' : ''}:</strong> ${escapeHtml(text)} <small>${time}</small>`;
            
            list.appendChild(msgDiv);
            list.scrollTop = list.scrollHeight;
        }
        
        function displaySystemMessage(text) {
            const list = document.getElementById('messageList');
            const msgDiv = document.createElement('div');
            msgDiv.className = 'message system';
            msgDiv.innerHTML = escapeHtml(text);
            list.appendChild(msgDiv);
            list.scrollTop = list.scrollHeight;
        }
        
        function escapeHtml(text) {
            const div = document.createElement('div');
            div.textContent = text;
            return div.innerHTML;
        }
        
        // 頁面關閉時通知
        window.addEventListener('beforeunload', () => {
            chatChannel.postMessage({
                type: 'leave',
                from: nickname,
                userId: userId
            });
            chatChannel.close();
        });
    </script>
</body>
</html>

場景3:數(shù)據(jù)同步

實現(xiàn)購物車在多標簽頁間的實時同步:

// cart-sync.js
class CartSync {
  constructor() {
    this.channel = new BroadcastChannel('cart_sync');
    this.items = this.loadFromStorage() || [];
    this.listeners = [];
    
    this.setupListener();
    this.syncWithOthers();
  }
  
  setupListener() {
    this.channel.onmessage = (event) => {
      const { type, data, from } = event.data;
      
      switch(type) {
        case 'cart_update':
          this.items = data.items;
          this.saveToStorage();
          this.notifyListeners('update', data);
          break;
          
        case 'cart_request':
          // 新標簽頁請求同步
          this.channel.postMessage({
            type: 'cart_response',
            data: { items: this.items },
            from: this.getTabId()
          });
          break;
          
        case 'cart_response':
          if (from !== this.getTabId() && this.items.length === 0) {
            this.items = data.items;
            this.saveToStorage();
            this.notifyListeners('sync', data);
          }
          break;
      }
    };
  }
  
  syncWithOthers() {
    // 請求其他標簽頁的數(shù)據(jù)
    this.channel.postMessage({
      type: 'cart_request',
      from: this.getTabId()
    });
  }
  
  addItem(item) {
    this.items.push({
      ...item,
      id: Date.now() + Math.random(),
      addedAt: new Date().toISOString()
    });
    
    this.broadcastUpdate();
  }
  
  removeItem(itemId) {
    this.items = this.items.filter(item => item.id !== itemId);
    this.broadcastUpdate();
  }
  
  updateQuantity(itemId, quantity) {
    const item = this.items.find(item => item.id === itemId);
    if (item) {
      item.quantity = Math.max(1, quantity);
      this.broadcastUpdate();
    }
  }
  
  broadcastUpdate() {
    this.saveToStorage();
    
    this.channel.postMessage({
      type: 'cart_update',
      data: { items: this.items },
      from: this.getTabId(),
      timestamp: Date.now()
    });
    
    this.notifyListeners('update', { items: this.items });
  }
  
  loadFromStorage() {
    const saved = localStorage.getItem('cart_items');
    return saved ? JSON.parse(saved) : null;
  }
  
  saveToStorage() {
    localStorage.setItem('cart_items', JSON.stringify(this.items));
  }
  
  getTabId() {
    let tabId = sessionStorage.getItem('tab_id');
    if (!tabId) {
      tabId = Math.random().toString(36).substring(2, 10);
      sessionStorage.setItem('tab_id', tabId);
    }
    return tabId;
  }
  
  subscribe(callback) {
    this.listeners.push(callback);
    return () => {
      this.listeners = this.listeners.filter(cb => cb !== callback);
    };
  }
  
  notifyListeners(event, data) {
    this.listeners.forEach(callback => callback(event, data));
  }
}
?
// 使用示例
const cart = new CartSync();
?
// 訂閱更新
cart.subscribe((event, data) => {
  console.log(`購物車${event}:`, data);
  updateCartUI(data.items);
});
?
// 添加商品
cart.addItem({
  name: '商品名稱',
  price: 99.9,
  quantity: 1
});

場景4:Web Worker 協(xié)作

// main.js
// 主線程
const workerChannel = new BroadcastChannel('worker_tasks');
const worker = new Worker('worker.js');
?
// 發(fā)送任務到所有worker
workerChannel.postMessage({
  type: 'new_task',
  taskId: 'task_001',
  data: [1, 2, 3, 4, 5]
});
?
// 接收worker結果
workerChannel.onmessage = (event) => {
  if (event.data.type === 'task_result') {
    console.log('任務完成:', event.data.result);
  }
};
?
// worker.js
// Web Worker
const channel = new BroadcastChannel('worker_tasks');
const workerId = Math.random().toString(36).substring(2, 6);
?
channel.onmessage = (event) => {
  const { type, taskId, data } = event.data;
  
  if (type === 'new_task') {
    console.log(`Worker ${workerId} 接收任務:`, taskId);
    
    // 模擬耗時計算
    const result = data.map(x => x * 2);
    
    // 廣播結果
    channel.postMessage({
      type: 'task_result',
      taskId: taskId,
      result: result,
      workerId: workerId
    });
  }
};

與其他通信方案的比較

1. vs localStorage

// localStorage 方案
window.addEventListener('storage', (e) => {
  if (e.key === 'message') {
    console.log('收到消息:', e.newValue);
  }
});
localStorage.setItem('message', 'hello');

// BroadcastChannel 方案
const channel = new BroadcastChannel('messages');
channel.onmessage = (e) => console.log('收到消息:', e.data);
channel.postMessage('hello');

優(yōu)勢對比

  • BroadcastChannel:專門為通信設計,語義清晰,性能更好,支持復雜數(shù)據(jù)類型
  • localStorage:主要用于存儲,通信只是附帶功能,有大小限制(通常5MB)

2. vs postMessage

// postMessage 需要知道目標窗口
const otherWindow = window.open('other.html');
otherWindow.postMessage('hello', '*');

// BroadcastChannel 無需知道目標
const channel = new BroadcastChannel('messages');
channel.postMessage('hello');

優(yōu)勢對比

  • BroadcastChannel:一對多廣播,無需維護窗口引用
  • postMessage:一對一通信,更靈活但需要管理目標

3. vs WebSocket

高級技巧

1. 頻道管理器

class BroadcastChannelManager {
  constructor() {
    this.channels = new Map();
    this.globalListeners = new Set();
  }
  
  // 獲取或創(chuàng)建頻道
  getChannel(name) {
    if (!this.channels.has(name)) {
      const channel = new BroadcastChannel(name);
      
      channel.onmessage = (event) => {
        // 觸發(fā)全局監(jiān)聽器
        this.globalListeners.forEach(listener => {
          listener(name, event.data, event);
        });
        
        // 觸發(fā)頻道特定監(jiān)聽器
        const channelListeners = this.channels.get(name)?.listeners || [];
        channelListeners.forEach(listener => {
          listener(event.data, event);
        });
      };
      
      this.channels.set(name, {
        channel,
        listeners: []
      });
    }
    
    return this.channels.get(name).channel;
  }
  
  // 訂閱頻道消息
  subscribe(channelName, listener) {
    this.getChannel(channelName); // 確保頻道存在
    
    const channel = this.channels.get(channelName);
    channel.listeners.push(listener);
    
    return () => {
      channel.listeners = channel.listeners.filter(l => l !== listener);
    };
  }
  
  // 訂閱所有頻道消息
  subscribeAll(listener) {
    this.globalListeners.add(listener);
    return () => this.globalListeners.delete(listener);
  }
  
  // 發(fā)送消息到頻道
  send(channelName, data) {
    const channel = this.getChannel(channelName);
    channel.postMessage(data);
  }
  
  // 關閉頻道
  closeChannel(channelName) {
    if (this.channels.has(channelName)) {
      const { channel } = this.channels.get(channelName);
      channel.close();
      this.channels.delete(channelName);
    }
  }
  
  // 關閉所有頻道
  closeAll() {
    this.channels.forEach(({ channel }) => channel.close());
    this.channels.clear();
    this.globalListeners.clear();
  }
}

// 使用示例
const manager = new BroadcastChannelManager();

// 訂閱特定頻道
const unsubscribe = manager.subscribe('chat', (data) => {
  console.log('聊天消息:', data);
});

// 訂閱所有頻道
const unsubscribeAll = manager.subscribeAll((channel, data) => {
  console.log(`[${channel}] 收到:`, data);
});

// 發(fā)送消息
manager.send('chat', { text: 'Hello' });

2. 消息確認機制

class ReliableBroadcastChannel {
  constructor(name) {
    this.channel = new BroadcastChannel(name);
    this.pendingMessages = new Map();
    this.messageId = 0;
    
    this.setupListener();
  }
  
  setupListener() {
    this.channel.onmessage = (event) => {
      const { type, id, data, from } = event.data;
      
      if (type === 'ack') {
        // 收到確認,移除待確認消息
        this.pendingMessages.delete(id);
      } else {
        // 處理消息
        this.handleMessage(data, from);
        
        // 發(fā)送確認
        this.channel.postMessage({
          type: 'ack',
          id: id,
          from: this.getSenderId()
        });
      }
    };
  }
  
  send(data, requireAck = true) {
    const id = ++this.messageId;
    
    this.channel.postMessage({
      type: 'message',
      id: id,
      data: data,
      from: this.getSenderId(),
      timestamp: Date.now()
    });
    
    if (requireAck) {
      // 存儲待確認消息
      this.pendingMessages.set(id, {
        data,
        timestamp: Date.now(),
        retries: 0
      });
      
      // 啟動重試機制
      this.startRetry(id);
    }
  }
  
  startRetry(id) {
    const maxRetries = 3;
    const timeout = 1000;
    
    const check = () => {
      const message = this.pendingMessages.get(id);
      
      if (message && message.retries < maxRetries) {
        message.retries++;
        console.log(`重發(fā)消息 ${id},第 ${message.retries} 次`);
        
        this.channel.postMessage({
          type: 'message',
          id: id,
          data: message.data,
          from: this.getSenderId(),
          retry: true
        });
        
        setTimeout(check, timeout * message.retries);
      } else if (message) {
        console.error(`消息 ${id} 發(fā)送失敗`);
        this.pendingMessages.delete(id);
      }
    };
    
    setTimeout(check, timeout);
  }
  
  handleMessage(data, from) {
    console.log('可靠收到:', data, '來自:', from);
  }
  
  getSenderId() {
    return sessionStorage.getItem('sender_id') || 
           Math.random().toString(36).substring(2);
  }
}

3. 心跳檢測和狀態(tài)同步

class TabHeartbeat {
  constructor() {
    this.channel = new BroadcastChannel('heartbeat');
    this.tabId = Math.random().toString(36).substring(2, 10);
    this.tabs = new Map();
    
    this.setupListener();
    this.startHeartbeat();
    this.requestStatus();
  }
  
  setupListener() {
    this.channel.onmessage = (event) => {
      const { type, tabId, data } = event.data;
      
      switch(type) {
        case 'heartbeat':
          this.updateTab(tabId, data);
          break;
          
        case 'status_request':
          this.sendStatus();
          break;
          
        case 'status_response':
          this.updateTab(tabId, data);
          break;
      }
    };
  }
  
  startHeartbeat() {
    // 每秒發(fā)送心跳
    setInterval(() => {
      this.channel.postMessage({
        type: 'heartbeat',
        tabId: this.tabId,
        data: {
          url: window.location.href,
          title: document.title,
          lastActive: Date.now(),
          scrollY: window.scrollY
        }
      });
    }, 1000);
    
    // 每30秒清理離線標簽
    setInterval(() => {
      this.cleanOfflineTabs();
    }, 30000);
  }
  
  requestStatus() {
    this.channel.postMessage({
      type: 'status_request',
      tabId: this.tabId
    });
  }
  
  sendStatus() {
    this.channel.postMessage({
      type: 'status_response',
      tabId: this.tabId,
      data: {
        url: window.location.href,
        title: document.title,
        lastActive: Date.now(),
        scrollY: window.scrollY
      }
    });
  }
  
  updateTab(tabId, data) {
    this.tabs.set(tabId, {
      ...data,
      lastSeen: Date.now()
    });
  }
  
  cleanOfflineTabs() {
    const now = Date.now();
    for (const [tabId, data] of this.tabs) {
      if (now - data.lastSeen > 5000) {
        this.tabs.delete(tabId);
      }
    }
  }
  
  getOnlineTabs() {
    return Array.from(this.tabs.values());
  }
}

降級方案

class CrossTabChannel {
  constructor(name) {
    this.name = name;
    this.listeners = [];
    
    if ('BroadcastChannel' in window) {
      // 使用 BroadcastChannel
      this.channel = new BroadcastChannel(name);
      this.channel.onmessage = (event) => {
        this.notifyListeners(event.data);
      };
    } else {
      // 降級到 localStorage
      this.setupLocalStorageFallback();
    }
  }
  
  setupLocalStorageFallback() {
    window.addEventListener('storage', (event) => {
      if (event.key === `channel_${this.name}` && event.newValue) {
        try {
          const data = JSON.parse(event.newValue);
          // 避免循環(huán)
          if (data.from !== this.getTabId()) {
            this.notifyListeners(data.payload);
          }
        } catch (e) {
          console.error('解析消息失敗:', e);
        }
      }
    });
  }
  
  postMessage(data) {
    if (this.channel) {
      // 使用 BroadcastChannel
      this.channel.postMessage(data);
    } else {
      // 使用 localStorage
      localStorage.setItem(`channel_${this.name}`, JSON.stringify({
        from: this.getTabId(),
        payload: data,
        timestamp: Date.now()
      }));
      // 立即清除,避免積累
      setTimeout(() => {
        localStorage.removeItem(`channel_${this.name}`);
      }, 100);
    }
  }
  
  onMessage(callback) {
    this.listeners.push(callback);
  }
  
  notifyListeners(data) {
    this.listeners.forEach(callback => callback(data));
  }
  
  getTabId() {
    let tabId = sessionStorage.getItem('tab_id');
    if (!tabId) {
      tabId = Math.random().toString(36).substring(2, 10);
      sessionStorage.setItem('tab_id', tabId);
    }
    return tabId;
  }
  
  close() {
    if (this.channel) {
      this.channel.close();
    }
    this.listeners = [];
  }
}

最佳實踐總結

1. 命名規(guī)范

// 使用清晰的命名空間
const channel = new BroadcastChannel('app_name:feature:room');
// 例如:'myapp:chat:room1', 'myapp:cart:sync'

2. 錯誤處理

channel.onmessageerror = (error) => {
  console.error('消息處理失敗:', error);
  // 可以嘗試重新發(fā)送或降級處理
};

3. 資源清理

// 組件卸載時關閉頻道
useEffect(() => {
  const channel = new BroadcastChannel('my_channel');
  
  return () => {
    channel.close();
  };
}, []);

4. 消息格式標準化

// 統(tǒng)一的消息格式
const message = {
  type: 'MESSAGE_TYPE',     // 消息類型
  id: 'unique_id',          // 唯一標識
  from: 'sender_id',        // 發(fā)送者
  payload: {},              // 實際數(shù)據(jù)
  timestamp: Date.now(),    // 時間戳
  version: '1.0'            // 版本號
};

5. 避免消息風暴

// 使用防抖或節(jié)流
function debounceBroadcast(fn, delay = 100) {
  let timer;
  return function(...args) {
    clearTimeout(timer);
    timer = setTimeout(() => fn.apply(this, args), delay);
  };
}
?
const debouncedSend = debounceBroadcast((data) => {
  channel.postMessage(data);
});

結語

BroadcastChannel API 為瀏覽器原生環(huán)境提供了一個簡單而強大的跨頁面通信解決方案。它不僅語法簡潔、性能優(yōu)秀,而且與現(xiàn)代Web開發(fā)范式完美契合。無論是構建實時協(xié)作應用、實現(xiàn)多標簽頁狀態(tài)同步,還是簡單的消息廣播,BroadcastChannel 都能優(yōu)雅地解決問題。

隨著瀏覽器支持的不斷完善,BroadcastChannel 必將成為Web開發(fā)中不可或缺的工具之一。希望本文能幫助您更好地理解和使用這個強大的API,在實際項目中發(fā)揮其最大價值。

到此這篇關于JS中BroadcastChannel瀏覽器原生跨標簽頁通信的文章就介紹到這了,更多相關JS  BroadcastChannel跨標簽頁通信內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

最新評論

巫溪县| 佛学| 镇远县| 永清县| 泰州市| 登封市| 育儿| 唐海县| 渭南市| 新丰县| 伊吾县| 澳门| 秦皇岛市| 沙坪坝区| 靖江市| 两当县| 全南县| 仙桃市| 巴塘县| 湘乡市| 洪江市| 望谟县| 班玛县| 佳木斯市| 临海市| 玛纳斯县| 秦安县| 夏邑县| 资源县| 应城市| 灌阳县| 汉阴县| 疏勒县| 惠水县| 陆河县| 刚察县| 佛坪县| 界首市| 崇州市| 根河市| 义马市|