页面之间的通信

JavaScript 是可以监控 localStorage 值的变化的。现代浏览器提供了一个名为 storage 的事件,当同一源下的其他窗口或标签页修改了 localStorage 时,这个事件会在当前窗口触发。监听 storage 事件可以实现实时监听 localStorage 的变化。

下面是如何监听 localStorage 变化的基本示例:

window.addEventListener('storage', function(event) {
  if (event.storageArea === localStorage) { // 确保事件来自localStorage
    if (event.key) { // key存在表示有项被修改、添加或删除
      console.log('Key "%s" was changed from "%s" to "%s"', 
                  event.key, 
                  event.oldValue, 
                  localStorage[event.key]);
    } else { // 如果key不存在,则可能是整个localStorage被清除
      console.log('The entire localStorage was cleared');
    }
  }
}, false);

请注意,storage 事件不会在触发改变的同一个窗口或标签页中触发,而是会在同一源下的其他打开的窗口或标签页中触发。这是因为 localStorage 在同源策略下是共享的,所以当一个窗口修改了 localStorage,其他所有相同源的窗口都能感知到这个变化。

此外,storage 事件对象提供了如下属性:

  • event.key: 被修改的键名。
  • event.oldValue: 键值修改前的旧值,如果键被删除则为 null
  • event.newValue: 键值修改后的值,如果键被删除也会是 null
  • event.url: 触发改变的文档的 URL。
  • event.storageArea: 修改数据的 Storage 对象引用,通常是 localStorage

然而,如果你想在一个窗口内部实时监听自身的 localStorage 变化,单纯依赖浏览器提供的 storage 事件是不够的,需要自行封装函数或利用 MutationObserver 等手段,但这并非标准做法,而且可能受到限制或无法稳定工作。在单窗口内实时监听 localStorage 通常需要在 setItem 等方法上进行包裹或替换,以便在调用时同步触发相应的事件。

对于浏览器标签页间的通信,以下是几种可行的方法:

  1. LocalStorage/SessionStorage
    通过监听 storage 事件,可以在不同的浏览器标签页间共享数据。当一个标签页修改了 localStoragesessionStorage,其他同源标签页会接收到 storage 事件通知,并可以从事件对象中获取变化的信息。

    window.addEventListener('storage', function(event) {
        if (event.key) {
            console.log('Key "%s" changed to "%s"', event.key, localStorage[event.key]);
        }
    });
    
    // 设置数据
    localStorage.setItem('message', 'Hello from another tab');
    
  2. Broadcast Channel API
    使用 BroadcastChannel 接口可以创建一个命名的通道,让同源的多个浏览器上下文(如标签页、iframe 等)能够互相通信。

    const channel = new BroadcastChannel('my-channel');
    
    // 发送消息
    channel.postMessage({ type: 'hello', data: 'From one tab' });
    
    // 接收消息
    channel.addEventListener('message', function(event) {
        console.log('Received message:', event.data);
    });
    
  3. WebSockets
    利用 WebSocket 技术,所有打开的标签页都可以连接到同一个服务器,通过服务器作为中介转发消息。

    var socket = new WebSocket('ws://example.com/mychannel');
    
    // 发送消息
    socket.send(JSON.stringify({ message: 'Hello from one tab' }));
    
    // 接收消息
    socket.onmessage = function(event) {
        console.log('Received via WebSocket:', JSON.parse(event.data));
    };
    
  4. Cookies + setInterval
    虽然不是理想的方案,但是也可以通过定时检查 Cookie 值的变化来间接实现通信,但这通常效率较低且不适合频繁通信。

  5. 同源窗口间自定义事件
    在同源的两个窗口之间,可以通过 window.postMessage 实现直接通信。

    // 发送消息的窗口
    otherWindow.postMessage('Hello from another tab', 'http://example.com');
    
    // 接收消息的窗口
    window.addEventListener('message', function(event) {
        if (event.origin === 'http://example.com') {
            console.log('Received message:', event.data);
        }
    });
    

选择哪种方式取决于具体的业务需求和技术背景。其中,Broadcast Channel API 和 window.postMessage 方法尤其适用于浏览器标签页间的直接通信。

面试官:前端跨页面通信,你知道哪些方法? - 掘金