введение
В браузере мы можем одновременно открывать несколько вкладок, и каждую вкладку можно грубо понимать как «независимую» рабочую среду, и даже глобальные объекты не будут использоваться несколькими вкладками. Однако иногда мы хотим иметь возможность синхронизировать данные страницы, информацию или состояние между этими «независимыми» страницами вкладок.
Так же, как в следующем примере: после того, как я нажму «Избранное» на странице списка, соответствующая кнопка страницы сведений будет автоматически обновлена до состояния «Избранное»; аналогичным образом, после нажатия «Избранное» на странице сведений кнопка в списке страница также будет обновляться.
Это то, что мы называем фронтальной межстраничной коммуникацией.
Какие методы межстраничной коммуникации вы знаете? Если непонятно, я покажу вам семь способов общения между страницами.
1. Межстраничная связь между страницами одного происхождения
следующими способамиОнлайн-демонстрация доступна здесь >>
браузерТа же политика происхожденияНекоторые методы межстраничной коммуникации, описанные ниже, по-прежнему имеют ограничения. Поэтому давайте сначала посмотрим, какие технологии можно использовать для достижения межстраничной связи при условии соблюдения политики одного и того же происхождения.
1. BroadCast Channel
BroadCast ChannelМожет помочь нам создать канал связи для трансляции. Когда все страницы прослушивают сообщения из одного и того же канала, сообщения, отправленные одной из них, будут получены всеми остальными страницами. Его API и использование очень просты.
Следующий способ может создать идентификатор какAlienZHOU
канал:
const bc = new BroadcastChannel('AlienZHOU');
Каждая страница доступна через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);
Конкретное использование широковещательного канала можно найти в этой статье.«[3-минутный обзор] Передняя широковещательная связь: широковещательный канал».
2. Service Worker
Service WorkerЭто Worker, который может работать в фоновом режиме в течение длительного времени и может осуществлять двустороннюю связь со страницей. Service Worker можно совместно использовать с несколькими страницами, а эффект широковещания можно получить, используя Service Worker в качестве центра обработки сообщений (центральной станции).
Service Worker также является одной из основных технологий в PWA.Поскольку эта статья не посвящена PWA, если вы хотите узнать больше о Service Worker, вы можете прочитать мою предыдущую статью.[Обучение и практика PWA] (3) Сделайте свое веб-приложение доступным в автономном режиме.
Сначала нужно зарегистрировать Service Worker на странице:
/* 页面逻辑 */
navigator.serviceWorker.register('../util.sw.js').then(function () {
console.log('Service Worker 注册成功');
});
в../util.sw.js
— это соответствующий сценарий Service Worker. Сам Service Worker автоматически не имеет функции «широковещательной связи», нам нужно добавить некоторый код, чтобы преобразовать его в станцию ретрансляции сообщений:
/* ../util.sw.js Service Worker 逻辑 */
self.addEventListener('message', function (e) {
console.log('service worker receive message', e.data);
e.waitUntil(
self.clients.matchAll().then(function (clients) {
if (!clients || clients.length === 0) {
return;
}
clients.forEach(function (client) {
client.postMessage(e.data);
});
})
);
});
Слушаем в Service Workermessage
События, получить информацию, отправленную страницей (называемой клиентом с точки зрения работника службы). затем пройтиself.clients.matchAll()
Получите все страницы, зарегистрированные в настоящее время в Service Worker, позвонив каждому клиенту (т.е. странице)postMessage
метод отправки сообщения на страницу. Это уведомляет другие страницы о сообщениях, полученных из одного места (вкладки).
После обработки Service Worker нам необходимо отслеживать сообщения, отправляемые Service Worker на странице:
/* 页面逻辑 */
navigator.serviceWorker.addEventListener('message', function (e) {
const data = e.data;
const text = '[receive] ' + data.msg + ' —— tab ' + data.from;
console.log('[Service Worker] receive message:', text);
});
Наконец, когда вам нужно синхронизировать сообщения, вы можете вызвать Service Worker’spostMessage
метод:
/* 页面逻辑 */
navigator.serviceWorker.controller.postMessage(mydata);
3. LocalStorage
LocalStorage — это наиболее часто используемое локальное хранилище во внешнем интерфейсе, и все должны быть хорошо знакомы с ним; ноStorageEvent
Это событие, связанное с ним, может быть незнакомо некоторым студентам.
Запускается при изменении LocalStoragestorage
мероприятие. Используя эту функцию, мы можем записать сообщение в LocalStorage при отправке сообщения, а затем на каждой странице, прослушиваяstorage
события, о которых нужно уведомлять.
window.addEventListener('storage', function (e) {
if (e.key === 'ctc-msg') {
const data = JSON.parse(e.newValue);
const text = '[receive] ' + data.msg + ' —— tab ' + data.from;
console.log('[Storage I] receive message:', text);
}
});
Добавьте приведенный выше код на каждую страницу, чтобы отслеживать изменения в LocalStorage. Когда странице нужно отправить сообщение, нам нужно только использовать знакомыйsetItem
Метод может быть:
mydata.st = +(new Date);
window.localStorage.setItem('ctc-msg', JSON.stringify(mydata));
Обратите внимание, здесь есть деталь: мы добавляем временную метку в mydata, которая принимает текущую миллисекунду.st
Атрибуты. Это потому что,storage
Событие срабатывает только тогда, когда значение действительно изменяется. Например:
window.localStorage.setItem('test', '123');
window.localStorage.setItem('test', '123');
благодаря второму значению'123'
То же значение, что и в первый раз, поэтому приведенный выше код будет работать только в первый раз.setItem
вызыватьstorage
мероприятие. Поэтому мы устанавливаемst
чтобы убедиться, что он будет запускаться каждый раз, когда он вызываетсяstorage
мероприятие.
Вздремнуть
Выше мы рассмотрели три способа реализации межстраничной связи, будь то широковещательный канал, устанавливающий широковещательный канал, или станция ретрансляции сообщений с использованием Service Worker, или какие-то хитрые способы.storage
События, которые все находятся в «режиме вещания»: страница уведомляет о сообщении на «центральную станцию», а «центральная станция» уведомляет каждую страницу.
В приведенном выше примере «центральная станция» может быть экземпляром BroadCast Channel, Service Worker или LocalStorage.
Ниже мы увидим два других способа обмена данными между страницами, которые я называю «Общее хранилище + режим опроса».
4. Shared Worker
Shared Workerявляется еще одним членом семьи рабочих. Обычные рабочие процессы работают независимо и не взаимодействуют друг с другом; общие рабочие процессы, зарегистрированные в нескольких вкладках, могут обмениваться данными.
Проблема с Shared Worker при реализации межстраничной связи заключается в том, что он не может активно уведомлять все страницы, поэтому мы будем использовать опрос для получения последних данных. Идея заключается в следующем:
Пусть совместный работник поддерживает два вида сообщений. Один пост, общий рабочий сохранит данные после его получения; другой получен, общий работник пройдет сохраненные данные после получения сообщенияpostMessage
на страницу, на которой он зарегистрирован. То есть пусть страница активно получает (синхронизирует) последние новости через get. Конкретная реализация выглядит следующим образом:
Во-первых, мы запустим Shared Worker на странице, способ запуска очень прост:
// 构造函数的第二个参数是 Shared Worker 名称,也可以留空
const sharedWorker = new SharedWorker('../util.shared.js', 'ctc');
Затем поддержите получение и отправку сообщений в Shared Worker:
/* ../util.shared.js: Shared Worker 代码 */
let data = null;
self.addEventListener('connect', function (e) {
const port = e.ports[0];
port.addEventListener('message', function (event) {
// get 指令则返回存储的消息数据
if (event.data.get) {
data && port.postMessage(data);
}
// 非 get 指令则存储该消息数据
else {
data = event.data;
}
});
port.start();
});
После этого страница периодически отправляет сообщение команды get в Shared Worker, опрашивает последние данные сообщения и отслеживает возвращаемую информацию на странице:
// 定时轮询,发送 get 指令的消息
setInterval(function () {
sharedWorker.port.postMessage({get: true});
}, 1000);
// 监听 get 消息的返回数据
sharedWorker.port.addEventListener('message', (e) => {
const data = e.data;
const text = '[receive] ' + data.msg + ' —— tab ' + data.from;
console.log('[Shared Worker] receive message:', text);
}, false);
sharedWorker.port.start();
Наконец, если вы хотите общаться между страницами, просто дайте Shared WorkerpostMessage
Только что:
sharedWorker.port.postMessage(mydata);
Обратите внимание, что если вы используете
addEventListener
Чтобы добавить прослушиватель сообщений Shared Worker, вам нужно явно вызватьMessagePort.start
метод, указанный вышеsharedWorker.port.start()
; если вы используетеonmessage
Связывание слушателей не требуется.
5. IndexedDB
Помимо использования Shared Worker для совместного использования данных хранилища, вы также можете использовать некоторые другие «глобальные» (поддерживающие межстраничные) решения для хранения. НапримерIndexedDBили куки.
Ввиду того, что вы уже знакомы с печеньем, и как «одно из самых ранних решений для хранения в Интернете», файлы cookie предприняли больше обязанностей в практических приложениях, чем они были первоначально разработаны. Мы будем использовать IndexedDB для реализации его ниже Отказ
Идея проста: подобно схеме Shared Worker, отправитель сообщения сохраняет сообщение в IndexedDB, а получатель (например, все страницы) получает самую свежую информацию путем опроса. Перед этим давайте просто инкапсулируем несколько методов инструмента IndexedDB.
- Открытое соединение с базой данных:
function openStore() {
const storeName = 'ctc_aleinzhou';
return new Promise(function (resolve, reject) {
if (!('indexedDB' in window)) {
return reject('don\'t support indexedDB');
}
const request = indexedDB.open('CTC_DB', 1);
request.onerror = reject;
request.onsuccess = e => resolve(e.target.result);
request.onupgradeneeded = function (e) {
const db = e.srcElement.result;
if (e.oldVersion === 0 && !db.objectStoreNames.contains(storeName)) {
const store = db.createObjectStore(storeName, {keyPath: 'tag'});
store.createIndex(storeName + 'Index', 'tag', {unique: false});
}
}
});
}
- Хранение данных
function saveData(db, data) {
return new Promise(function (resolve, reject) {
const STORE_NAME = 'ctc_aleinzhou';
const tx = db.transaction(STORE_NAME, 'readwrite');
const store = tx.objectStore(STORE_NAME);
const request = store.put({tag: 'ctc_data', data});
request.onsuccess = () => resolve(db);
request.onerror = reject;
});
}
- запрос/чтение данных
function query(db) {
const STORE_NAME = 'ctc_aleinzhou';
return new Promise(function (resolve, reject) {
try {
const tx = db.transaction(STORE_NAME, 'readonly');
const store = tx.objectStore(STORE_NAME);
const dbRequest = store.get('ctc_data');
dbRequest.onsuccess = e => resolve(e.target.result);
dbRequest.onerror = reject;
}
catch (err) {
reject(err);
}
});
}
В остальном работа очень проста. Сначала откройте подключение к данным и инициализируйте данные:
openStore().then(db => saveData(db, null))
Для чтения сообщения вы можете опросить после подключения и инициализации:
openStore().then(db => saveData(db, null)).then(function (db) {
setInterval(function () {
query(db).then(function (res) {
if (!res || !res.data) {
return;
}
const data = res.data;
const text = '[receive] ' + data.msg + ' —— tab ' + data.from;
console.log('[Storage I] receive message:', text);
});
}, 1000);
});
Наконец, когда вы хотите отправить сообщение, просто сохраните данные в IndexedDB:
openStore().then(db => saveData(db, null)).then(function (db) {
// …… 省略上面的轮询代码
// 触发 saveData 的方法可以放在用户操作的事件监听内
saveData(db, mydata);
});
Вздремнуть
Помимо «режима вещания» мы также узнали о режиме «общее хранилище + долгий опрос». Вам может показаться, что длительный опрос не так элегантен, как режим прослушивания, но на самом деле иногда при использовании формы «общего хранилища» использовать длительный опрос не обязательно.
Например, в сценарии с несколькими вкладками мы можем оставить вкладку A для работы на другой вкладке B; когда мы переключаемся с вкладки B обратно на вкладку A через некоторое время, мы надеемся синхронизировать информацию о предыдущей операции на вкладке B обратно. . В настоящее время он используется только для мониторинга на вкладке A.visibilitychange
Для такого события достаточно сделать синхронизацию информации.
Далее я представлю еще один метод общения, который я называю моделью «сарафанного радио».
6. window.open + window.opener
когда мы используемwindow.open
Когда страница открыта, метод возвращает открытую страницуwindow
цитаты. в то время как указанныйnoopener
, на открытую страницу можно попасть черезwindow.opener
Получить ссылку на страницу, на которой она была открыта — таким образом мы соединяем страницы (древовидная структура).
Во-первых, мы положилиwindow.open
открытой страницыwindow
Объекты собираются:
let childWins = [];
document.getElementById('btn').addEventListener('click', function () {
const win = window.open('./some/sample');
childWins.push(win);
});
Затем, когда нам нужно отправить сообщение, как инициатору сообщения, страница должна уведомить как страницу, которую она открывает, так и страницу, которая ее открывает:
// 过滤掉已经关闭的窗口
childWins = childWins.filter(w => !w.closed);
if (childWins.length > 0) {
mydata.fromOpenner = false;
childWins.forEach(w => w.postMessage(mydata));
}
if (window.opener && !window.opener.closed) {
mydata.fromOpenner = true;
window.opener.postMessage(mydata);
}
Обратите внимание, что я использую.closed
Свойство отфильтровывает окна вкладок, которые были закрыты. Таким образом, задача быть отправителем сообщения выполнена. Давайте посмотрим, что он должен делать в качестве получателя сообщений.
На данный момент страница, которая получает сообщение, не может быть такой эгоистичной.Помимо отображения полученного сообщения, она также должна передать сообщение «людям, которых она знает» (открытие и открытие ею):
Следует отметить, что, оценивая источник сообщения, я избегаю отправки сообщения обратно отправителю и предотвращаю передачу сообщения в бесконечном цикле между ними. (В этой схеме будут и другие мелкие проблемы, которые можно дополнительно оптимизировать на практике)
window.addEventListener('message', function (e) {
const data = e.data;
const text = '[receive] ' + data.msg + ' —— tab ' + data.from;
console.log('[Cross-document Messaging] receive message:', text);
// 避免消息回传
if (window.opener && !window.opener.closed && data.fromOpenner) {
window.opener.postMessage(data);
}
// 过滤掉已经关闭的窗口
childWins = childWins.filter(w => !w.closed);
// 避免消息回传
if (childWins && !data.fromOpenner) {
childWins.forEach(w => w.postMessage(data));
}
});
Таким образом, каждый узел (страница) несет ответственность за доставку сообщений, что я называю «сарафанным радио», и сообщения циркулируют в этой древовидной структуре.
Вздремнуть
Очевидно, есть проблема с моделью «сарафанное радио»: если страница неwindow.open
Открытая (например, введенная непосредственно в адресную строку или ссылка с другого веб-сайта), ссылка не работает.
В дополнение к вышеупомянутым шести распространенным методам на самом деле существует (седьмой) метод синхронизации с помощью технологии «проталкивания сервера», такой как WebSocket. Это похоже на перемещение нашей «центральной станции» из переднего конца в задний.
О WebSocket и других технологиях "проталкивания сервера" студенты, которые не понимают, могут прочитать эту статью.«Принципы и примеры различных технологий «Server Push» (опрос/COMET/SSE/WebSocket)»
Кроме того, я также написалДемонстрация онлайн-демонстрации >>
2. Связь между страницами разного происхождения
Выше мы ввели метод семи напротив раскол, но большинство из них ограничены гомологичными стратегиями. Однако иногда у нас есть два разных домены линий продуктов, и мы надеемся, что все страницы ниже могут передавать доступность. Что я должен делать?
Для этого в качестве «моста» можно использовать невидимый для пользователя iframe. Поскольку iframe и родительскую страницу можно указать, указавorigin
Чтобы игнорировать ограничение одинакового происхождения, чтобы вы могли встроить iFrame на страницу (например,:http://sample.com/bridge.html
), и эти фреймы принадлежат одной и той же исходной странице, потому что они используют URL-адрес, а их методы связи могут повторно использовать различные методы, упомянутые в первой части выше.
Связь между страницей и iframe очень проста.Во-первых, вам нужно отслеживать сообщение, отправленное iframe на странице, и выполнять соответствующую бизнес-обработку:
/* 业务页面代码 */
window.addEventListener('message', function (e) {
// …… do something
});
Затем, когда страница хочет связаться с другими страницами того же или другого происхождения, она сначала отправит сообщение в iframe:
/* 业务页面代码 */
window.frames[0].window.postMessage(mydata, '*');
что для простоты будетpostMessage
Второй параметр установлен в'*'
, вы также можете установить URL-адрес iframe. После того, как iframe получает сообщение, он использует какой-то метод обмена сообщениями между страницами для синхронизации сообщения во всех iframe, например широковещательный канал, используемый ниже:
/* iframe 内代码 */
const bc = new BroadcastChannel('AlienZHOU');
// 收到来自页面的消息后,在 iframe 间进行广播
window.addEventListener('message', function (e) {
bc.postMessage(e.data);
});
Когда другие фреймы получают уведомление, они синхронизируют сообщение со страницей, которой они принадлежат:
/* iframe 内代码 */
// 对于收到的(iframe)广播消息,通知给所属的业务页面
bc.onmessage = function (e) {
window.parent.postMessage(e.data, '*');
};
На следующем рисунке показан шаблон связи между негомологичными страницами с использованием iframe в качестве «моста».
Среди них «гомологическая схема междоменной связи» может использовать одну из технологий, упомянутых в первой части статьи.
Суммировать
Сегодня я поделюсь с вами различными способами межстраничной коммуникации.
Для страниц того же происхождения распространены следующие способы:
- Широковещательный режим: широковещательный канал / сервисный работник / LocalStorage + StorageEvent
- Режим общего хранилища: Shared Worker/IndexedDB/cookie
- Режим сарафанного радио: window.open + window.opener
- Серверные: Websocket/Comet/SSE и т. д.
Для страниц разного происхождения вы можете преобразовать сообщения страницы другого происхождения в сообщения страницы того же происхождения, внедрив iframe того же происхождения в качестве «моста».
Услуги для студентов приветствуют вниманиеМой блог >> https://github.com/alienzhou/blog
В то же время, когда вы делитесь этой статьей, она также предназначена для метания и цитирования нефрита. Если у вас есть другие идеи, приглашаем обсудить их вместе и выдвинуть свои мнения и идеи~