JavaScript BroadcastChannel — межвкладочное взаимодействие

27 августа 2026
Автор

Антон Ларичев

Что такое BroadcastChannel

BroadcastChannel — это браузерный API, который позволяет разным контекстам одного происхождения (вкладкам, окнам, iframe, воркерам) обмениваться сообщениями в режиме «один ко всем». Достаточно создать канал с одинаковым именем в нескольких контекстах — и они автоматически начнут получать сообщения друг от друга.

АPI доступно в браузерах без какой-либо установки пакетов. Оно работает в рамках одного origin — протокол, домен и порт должны совпадать. Вкладки https://example.com и http://example.com находятся в разных origin и не смогут общаться через один канал.

Курс по теме

Основы JavaScript — курс

40 000+ студентов · рейтинг 4.8 · гарантия возврата 30 дней

Создание канала

Для создания канала используется конструктор BroadcastChannel, которому передаётся строка-имя:

const channel = new BroadcastChannel('notifications');

Каждый контекст, создавший канал с именем 'notifications', становится его участником. Имя канала — произвольная строка; рекомендуется выбирать осмысленные имена, чтобы разные части приложения не конфликтовали друг с другом.

Отправка сообщений

Отправка выполняется методом postMessage. Данные сериализуются по алгоритму структурированного клонирования, поэтому можно передавать не только строки, но и объекты, массивы, Map, Set, ArrayBuffer и другие типы.

const channel = new BroadcastChannel('cart');

// Отправить обычный объект
channel.postMessage({
  type: 'ITEM_ADDED',
  payload: { id: 42, name: 'Книга по JavaScript', quantity: 1 }
});

// Отправить строку
channel.postMessage('ping');

// Отправить массив
channel.postMessage([1, 2, 3]);

Важно: отправитель не получает собственное сообщение. postMessage доставляет данные только другим контекстам, подписанным на тот же канал.

Получение сообщений

Для получения сообщений нужно назначить обработчик события message через свойство onmessage или метод addEventListener:

const channel = new BroadcastChannel('cart');

// Через свойство
channel.onmessage = (event) => {
  console.log('Получено:', event.data);
};

// Через addEventListener (предпочтительно — можно добавить несколько обработчиков)
channel.addEventListener('message', (event) => {
  console.log('Получено:', event.data);
});

Объект события содержит:

  • event.data — переданные данные;
  • event.origin — origin отправителя;
  • event.lastEventId — идентификатор события (обычно пустая строка);
  • event.source — всегда null для BroadcastChannel.

Обработка ошибок

Если данные не удаётся сериализовать (например, при передаче объекта с циклическими ссылками или функциями), генерируется исключение. Дополнительно можно обработать событие messageerror — оно возникает, когда полученное сообщение не удаётся десериализовать:

const channel = new BroadcastChannel('sync');

channel.addEventListener('messageerror', (event) => {
  console.error('Ошибка десериализации сообщения', event);
});

// Попытка отправить несериализуемые данные вызовет исключение
try {
  const circular = {};
  circular.self = circular;
  channel.postMessage(circular); // DataCloneError
} catch (error) {
  console.error('Ошибка отправки:', error.message);
}

Закрытие канала

Когда канал больше не нужен, его следует закрыть методом close. После закрытия канал перестаёт получать сообщения и высвобождает ресурсы:

const channel = new BroadcastChannel('session');

// Закрываем при уходе пользователя со страницы
window.addEventListener('beforeunload', () => {
  channel.close();
});

Закрытый канал нельзя переоткрыть — нужно создавать новый экземпляр.

Практический пример: синхронизация корзины

Представьте интернет-магазин: пользователь открывает несколько вкладок и добавляет товары в корзину. Без синхронизации вкладки будут показывать разное содержимое.

// cart.js — модуль, подключаемый на каждой странице
const cartChannel = new BroadcastChannel('shopping-cart');

let cartItems = JSON.parse(localStorage.getItem('cart') || '[]');

function addItem(item) {
  cartItems.push(item);
  localStorage.setItem('cart', JSON.stringify(cartItems));

  // Уведомляем остальные вкладки
  cartChannel.postMessage({
    type: 'CART_UPDATED',
    items: cartItems
  });

  renderCart();
}

function removeItem(itemId) {
  cartItems = cartItems.filter(i => i.id !== itemId);
  localStorage.setItem('cart', JSON.stringify(cartItems));

  cartChannel.postMessage({
    type: 'CART_UPDATED',
    items: cartItems
  });

  renderCart();
}

// Слушаем изменения из других вкладок
cartChannel.addEventListener('message', (event) => {
  if (event.data.type === 'CART_UPDATED') {
    cartItems = event.data.items;
    renderCart();
  }
});

function renderCart() {
  const counter = document.querySelector('.cart-counter');
  if (counter) {
    counter.textContent = cartItems.length;
  }
}

Практический пример: выход из аккаунта во всех вкладках

Это одна из самых распространённых задач: когда пользователь выходит из системы, нужно закрыть сессию во всех открытых вкладках.

// auth.js
const authChannel = new BroadcastChannel('auth');

function logout() {
  // Чистим локальное состояние
  localStorage.removeItem('token');
  sessionStorage.clear();

  // Сообщаем всем остальным вкладкам
  authChannel.postMessage({ type: 'LOGOUT' });

  // Перенаправляем текущую вкладку
  window.location.href = '/login';
}

function login(token) {
  localStorage.setItem('token', token);

  authChannel.postMessage({ type: 'LOGIN', token });
}

// Реагируем на события других вкладок
authChannel.addEventListener('message', (event) => {
  switch (event.data.type) {
    case 'LOGOUT':
      // Другая вкладка разлогинилась — делаем то же самое
      localStorage.removeItem('token');
      window.location.href = '/login';
      break;

    case 'LOGIN':
      // Другая вкладка вошла — обновляем токен
      localStorage.setItem('token', event.data.token);
      break;
  }
});

Практический пример: синхронизация темы оформления

// theme.js
const themeChannel = new BroadcastChannel('ui-theme');

function setTheme(theme) {
  document.documentElement.dataset.theme = theme;
  localStorage.setItem('theme', theme);

  themeChannel.postMessage({ theme });
}

themeChannel.addEventListener('message', (event) => {
  document.documentElement.dataset.theme = event.data.theme;
});

// Инициализация при загрузке страницы
const savedTheme = localStorage.getItem('theme') || 'light';
document.documentElement.dataset.theme = savedTheme;

Использование в Service Worker

BroadcastChannel работает и в Service Worker — это позволяет отправлять уведомления из воркера во все вкладки приложения:

// service-worker.js
const swChannel = new BroadcastChannel('sw-updates');

self.addEventListener('push', (event) => {
  const data = event.data.json();

  // Показываем уведомление
  event.waitUntil(
    self.registration.showNotification(data.title, {
      body: data.body
    })
  );

  // Уведомляем все открытые вкладки
  swChannel.postMessage({
    type: 'PUSH_RECEIVED',
    payload: data
  });
});

// app.js (на стороне страницы)
const swChannel = new BroadcastChannel('sw-updates');

swChannel.addEventListener('message', (event) => {
  if (event.data.type === 'PUSH_RECEIVED') {
    showInAppNotification(event.data.payload);
  }
});

Паттерн: запрос состояния при открытии новой вкладки

Новая вкладка может запросить актуальное состояние у уже открытых вкладок:

const stateChannel = new BroadcastChannel('app-state');

let appState = {
  user: null,
  theme: 'light',
  language: 'ru'
};

stateChannel.addEventListener('message', (event) => {
  switch (event.data.type) {
    case 'REQUEST_STATE':
      // Другая вкладка просит поделиться состоянием
      stateChannel.postMessage({
        type: 'STATE_RESPONSE',
        state: appState
      });
      break;

    case 'STATE_RESPONSE':
      // Получили состояние от другой вкладки
      if (!appState.user) {
        appState = event.data.state;
        applyState(appState);
      }
      break;

    case 'STATE_CHANGED':
      appState = { ...appState, ...event.data.changes };
      applyState(appState);
      break;
  }
});

function updateState(changes) {
  appState = { ...appState, ...changes };
  stateChannel.postMessage({ type: 'STATE_CHANGED', changes });
  applyState(appState);
}

function applyState(state) {
  document.documentElement.dataset.theme = state.theme;
  document.documentElement.lang = state.language;
  // ...
}

// При загрузке страницы запрашиваем состояние у существующих вкладок
stateChannel.postMessage({ type: 'REQUEST_STATE' });

BroadcastChannel vs альтернативы

LocalStorage события

До появления BroadcastChannel разработчики синхронизировали вкладки через событие storage на объекте window. Этот подход требует сохранения данных в localStorage — даже если они нужны только для передачи сообщения:

// Старый подход
window.addEventListener('storage', (event) => {
  if (event.key === 'cart-update') {
    const data = JSON.parse(event.newValue);
    updateCart(data);
  }
});

localStorage.setItem('cart-update', JSON.stringify(cartData));

BroadcastChannel чище: не засоряет localStorage и позволяет передавать богатые типы данных без ручной сериализации.

SharedWorker

SharedWorker — более мощный инструмент: позволяет выполнять код в отдельном потоке и управлять сложной логикой централизованно. Но он требует написания отдельного файла воркера и более сложного API. BroadcastChannel подходит для простой pub/sub коммуникации.

postMessage через window.opener

Для общения с конкретным окном можно использовать window.opener.postMessage. Но этот способ работает только между окном и его открывателем — нельзя обратиться к произвольной вкладке.

Поддержка браузеров

BroadcastChannel поддерживается во всех современных браузерах: Chrome 54+, Firefox 38+, Edge 79+, Safari 15.4+. В Node.js API недоступно нативно, но существуют полифилы для тестирования.

Если требуется поддержка Safari до 15.4 или Internet Explorer, нужен полифил на основе localStorage.

Итог

BroadcastChannel — элегантный и простой API для синхронизации состояния между вкладками. Он подходит для задач, которые раньше решались через localStorage-хаки: выход из системы, синхронизация корзины, обновление темы, рассылка уведомлений из Service Worker.

Основные правила работы с BroadcastChannel:

  • Один канал — одно имя, все участники с этим именем получают сообщения.
  • Отправитель не получает собственные сообщения.
  • Данные сериализуются структурным клонированием — функции и циклические ссылки не поддерживаются.
  • Всегда закрывайте канал методом close(), когда он больше не нужен.
  • API работает только в пределах одного origin.

Для глубокого изучения JavaScript, включая браузерные API, асинхронное программирование и работу с событиями, рекомендую курс JavaScript на PurpleSchool.

Стрелочка влевоСобытие change в JavaScriptСобытие beforeunload в JavaScriptСтрелочка вправо

Постройте личный план изучения Javascript до уровня Middle — бесплатно!

Javascript — часть карты развития Frontend

  • step100+ шагов развития
  • lessons30 бесплатных лекций
  • lessons300 бонусных рублей на счет

Бесплатные лекции

Все гайды по Javascript

Как работает метод trim() - JavaScriptКак работает метод toUpperCase() - JavaScriptКак работает метод toLowerCase() - JavaScriptКак работает метод substring() - JavaScriptКак работает метод startsWith() - JavaScriptКак работает метод split() - JavaScriptКак работает метод slice() - JavaScriptКак работает метод search() - JavaScriptКак работает метод replaceAll() - JavaScriptКак работает метод replace() - JavaScriptКак работает метод repeat() - JavaScriptКак работает метод padStart() - JavaScriptКак работает метод padEnd() - JavaScriptКак работает метод matchAll() - JavaScriptКак работает метод match() - JavaScriptКак работает метод localeCompare() - JavaScriptКак работает свойство length - JavaScriptКак работает метод lastIndexOf() - JavaScriptКак работает метод indexOf() - JavaScriptКак работает метод includes() - JavaScriptКак работает метод fromCodePoint() - JavaScriptКак работает метод fromCharCode() - JavaScriptКак работает метод endsWith() - JavaScriptКак работает метод concat() - JavaScriptКак работает метод codePointAt() - JavaScriptКак работает метод charCodeAt() - JavaScriptКак работает метод charAt() - JavaScript
Итератор в JavaScript
try...catch в JavaScriptError в JavaScript
Событие wheel в JavaScriptСобытие unload в JavaScriptСобытие touch в JavaScriptСобытие submit в JavaScriptСобытие scroll в JavaScriptСобытие reset в JavaScriptМетод .preventDefault() в JavaScriptСобытие mouseover в JavaScriptСобытие mouseout в JavaScriptСобытие load в JavaScriptСобытие keyup в JavaScriptСобытие keydown в JavaScriptСобытие invalid в JavaScriptСобытие input в JavaScriptСобытийная модель Event в JavaScriptОбъект события Event в JavaScriptСобытие DOMContentLoaded в JavaScriptСобытие click в JavaScriptСобытие dblclick в JavaScriptСобытие change в JavaScriptJavaScript BroadcastChannel — межвкладочное взаимодействиеСобытие beforeunload в JavaScript
Как работает метод some() - JavaScriptКак работает метод reverse() - JavaScriptКак работает метод reduce() - JavaScriptКак работает метод map() - JavaScriptКак работает метод isArray() - JavaScriptКак работает метод indexOf() - JavaScriptКак работает метод includes() - JavaScriptКак работает метод from() - JavaScriptКак работает метод forEach() - JavaScriptКак работает метод flatMap() - JavaScriptКак работает метод flat() - JavaScriptКак работает метод findIndex() - JavaScriptКак работает метод find() - JavaScriptКак работает метод filter() - JavaScriptКак работает метод every() - JavaScriptМассивы в JavaScriptArray.at, findLast, findLastIndex — новые методы массивов
Открыть базу знаний

Лучшие курсы по теме

изображение курса

Основы JavaScript

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.8
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

TypeScript с нуля

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.8
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

Next.js - с нуля

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.7
3 999 ₽ 6 990 ₽
Подробнее

Отправить комментарий