首页/文章/javascript

如何在页签中监听 LocalStorage 变化?

2024-09-24
24419 分钟
...

在开发中,你是否会碰到过这样的需求:需要监听 LocalStorage 的变化。这在不同浏览器页签间是相对简单的,因为浏览器提供了内置的 storage 事件。但在同一个浏览器页签下,却没有直接的方式实现。今天,我们探讨下有几种高效且易用的解决方案,可以帮助轻松应对这种需求!

传统方案的痛点

先来看看浏览器是如何帮助我们处理不同页签的 LocalStorage 变化:

window.addEventListener("storage", (event) => {      
  if (event.key === "myKey") {
    
  }
});

通过监听 storage 事件,当在其他页签修改 LocalStorage 时,你可以在当前页签捕获到这个变化。但问题是:这种方法只适用于跨页签的 LocalStorage 修改,在同一页签下无法触发该事件。于是,很多开发者开始寻求替代方案,比如:

1、轮询(Polling)

轮询是一种最直观的方式,它定期检查 localStorage 的值是否发生变化。然而,这种方法性能较差,尤其在高频轮询时会对浏览器性能产生较大的影响,因此不适合作为长期方案。

let lastValue = localStorage.getItem('myKey');

setInterval(() => {
  const newValue = localStorage.getItem('myKey');
  if (newValue !== lastValue) {
    lastValue = newValue;
    console.log('Detected localStorage change:', newValue);
  }
}, 1000); 

这种方式实现简单,不依赖复杂机制。但是性能较差,频繁轮询会影响浏览器性能。

2、监听代理(Proxy)或发布-订阅模式

这种方式通过创建一个代理来拦截 localStorage.setItem 的调用。每次数据变更时,我们手动发布一个事件,通知其他监听者。

(function() {
  const originalSetItem = localStorage.setItem;
  const subscribers = [];

  localStorage.setItem = function(key, value) {
    originalSetItem.apply(this, arguments);
    subscribers.forEach(callback => callback(key, value));
  };

  function subscribe(callback) {
    subscribers.push(callback);
  }

  subscribe((key, value) => {
    if (key === 'myKey') {
      console.log('Detected localStorage change:', value);
    }
  });

  localStorage.setItem('myKey', 'newValue');
})();

这种比较灵活,可以用于复杂场景。但是需要手动拦截 setItem,维护成本高(但也是值得推荐的)。

然而,这些方案往往存在性能问题或者开发的复杂度,在高频数据更新的情况下,有一定的性能问题,而且存在一定的风险性。那么有没有可以简单快速,风险性还小的方案呢?

高效的解决方案

既然浏览器不支持同一页签的 storage 事件,我们可以手动触发事件,以此来实现同一页签下的 LocalStorage 变化监听。

1、自定义 Storage 事件

通过手动触发 StorageEvent,你可以在 LocalStorage 更新时同步分发事件,从而实现同一页签下的监听。

localStorage.setItem('myKey', 'value');


const storageEvent = new StorageEvent('storage', {
  key: 'myKey',
  url: window.location.href
});
window.dispatchEvent(storageEvent);

你可以使用相同的监听逻辑来处理数据变化,无论是同一页签还是不同页签:

window.addEventListener("storage", (event) => {
  if (event.key === "myKey") {
    
  }
});

这种实现简单、轻量、快捷。但是需要手动触发事件。

2、基于 CustomEvent 的自定义事件

StorageEvent 类似,你可以使用 CustomEvent 手动创建并分发事件,实现 localStorage 的同步监听。

localStorage.setItem('myKey', 'newValue');

const customEvent = new CustomEvent('localStorageChange', {
  detail: { key: 'myKey', value: 'newValue' }
});
window.dispatchEvent(customEvent);

这种方式适合更加灵活的事件触发场景。CustomEvent不局限于 localStorage 事件,可以扩展到其他功能。

window.addEventListener('localStorageChange', (event) => {
  const { key, value } = event.detail;
  if (key === 'myKey') {
    console.log('Detected localStorage change:', value);
  }
});
3、MessageChannel(消息通道)

MessageChannel API 可以在同一个浏览器上下文中发送和接收消息。我们可以通过 MessageChannellocalStorage 的变化信息同步到其他部分,起到类似事件监听的效果。

const channel = new MessageChannel();

channel.port1.onmessage = (event) => {
  console.log('Detected localStorage change:', event.data);
};

localStorage.setItem('myKey', 'newValue');
channel.port2.postMessage(localStorage.getItem('myKey'));

适合组件通信和复杂应用场景,消息机制较为灵活。相对复杂的实现,可能不适合简单场景。

4、BroadcastChannel

BroadcastChannel 提供了一种更高级的浏览器通信机制,允许多个窗口或页面之间广播消息。你可以通过这个机制将 localStorage 变更同步到多个页面或同一页面的不同部分。

const channel = new BroadcastChannel('storage_channel');

channel.onmessage = (event) => {
  console.log('Detected localStorage change:', event.data);
};

localStorage.setItem('myKey', 'newValue');
channel.postMessage({ key: 'myKey', value: 'newValue' });

支持跨页面通信,方便在不同页面间同步数据,易于实现。适用场景较为具体,通常用于复杂的页面通信需求。

这4个方法,主打的就是一个见缝插针,简单快速,风险性低。但是客观角度来讲,每种方案都是有各自优势的。

优势对比

方案优点缺点适用场景
轮询实现简单,适合低频监控需求性能差,频繁轮询影响浏览器性能简单场景或临时方案
监听代理/发布-订阅模式灵活扩展,适合复杂项目需要手动拦截 setItem,维护成本高需要手动事件发布的场景
自定义 StorageEvent实现简单,原生支持 storage 事件监听需要手动触发事件同页签下 localStorage 监听
自定义事件灵活的事件管理,适合不同场景需要手动触发事件需要自定义触发条件的场景
MessageChannel适合组件通信和复杂应用场景实现复杂,不适合简单场景高级组件通信需求
BroadcastChannel跨页面通信,适合复杂通信需求使用场景较具体复杂的多窗口通信

如何在 React / Vue 使用

在主流前端框架(如 React 和 Vue)中,监听 LocalStorage 变化并不困难。无论是 React 还是 Vue,你都可以使用自定义的 StorageEvent 或其他方法来实现监听。在此,我们以自定义 StorageEvent 为例,展示如何在 React 和 Vue 中实现 LocalStorage 的监听。

1. 在 React 中使用自定义 StorageEvent

React 是一个基于组件的框架,我们可以使用 React 的生命周期函数(如 useEffect)来监听和处理 LocalStorage 的变化。

import React, { useEffect } from 'react';

const LocalStorageListener = () => {
  useEffect(() => {
    
    const handleStorageChange = (event) => {
      if (event.key === 'myKey') {
        console.log('Detected localStorage change:', event.newValue);
      }
    };

    
    window.addEventListener('storage', handleStorageChange);

    
    const triggerCustomStorageEvent = () => {
      const storageEvent = new StorageEvent('storage', {
        key: 'myKey',
        newValue: 'newValue',
        url: window.location.href,
      });
      window.dispatchEvent(storageEvent);
    };

    
    return () => {
      window.removeEventListener('storage', handleStorageChange);
    };
  }, []); 

  return (
    <div>
      <button onClick={() => localStorage.setItem('myKey', 'newValue')}>
        修改 localStorage
      </button>
      <button onClick={() => window.dispatchEvent(new StorageEvent('storage', {
        key: 'myKey',
        newValue: localStorage.getItem('myKey'),
        url: window.location.href,
      }))}>
        手动触发 StorageEvent
      </button>
    </div>
  );
};

export default LocalStorageListener;
  • useEffect 是 React 的一个 Hook,用来处理副作用,在这里我们用它来注册和清除事件监听器。
  • 我们手动触发了 StorageEvent,以便在同一页面中监听 LocalStorage 的变化。
2. 在 Vue 中使用自定义 StorageEvent

在 Vue 3 中,我们可以使用 onMountedonUnmounted 这两个生命周期钩子来管理事件监听器。(Vue 3 Composition API):

<template>
  <div>
    <button @click="updateLocalStorage">修改 localStorage</button>
    <button @click="triggerCustomStorageEvent">手动触发 StorageEvent</button>
  </div>
</template>

<script lang="ts" setup>
import { onMounted, onUnmounted } from 'vue';

const handleStorageChange = (event: StorageEvent) => {
  if (event.key === 'myKey') {
    console.log('Detected localStorage change:', event.newValue);
  }
};

const updateLocalStorage = () => {
  localStorage.setItem('myKey', 'newValue');
};

const triggerCustomStorageEvent = () => {
  const storageEvent = new StorageEvent('storage', {
    key: 'myKey',
    newValue: 'newValue',
    url: window.location.href,
  });
  window.dispatchEvent(storageEvent);
};

onMounted(() => {
  window.addEventListener('storage', handleStorageChange);
});

onUnmounted(() => {
  window.removeEventListener('storage', handleStorageChange);
});
</script>
  • 使用了 Vue 的 Composition API,其中 onMountedonUnmounted 类似于 React 的 useEffect,用于在组件挂载和卸载时管理副作用。
  • 同样手动触发了 StorageEvent 来监听同一页面中的 LocalStorage 变化。

提炼封装一下

无论是 React 还是 Vue,将自定义 StorageEvent 实现为一个组件或工具函数是常见的做法。你可以将上面的逻辑提取到一个独立的 hook 或工具函数中,方便在项目中多次使用。

在 React 中提取为 Hook
import { useEffect } from 'react';

const useLocalStorageListener = (key, callback) => {
  useEffect(() => {
    const handleStorageChange = (event) => {
      if (event.key === key) {
        callback(event.newValue);
      }
    };

    window.addEventListener('storage', handleStorageChange);

    return () => {
      window.removeEventListener('storage', handleStorageChange);
    };
  }, [key, callback]);
};

export default useLocalStorageListener;
在 Vue 中提取为工具函数
import { onMounted, onUnmounted } from 'vue';

export const useLocalStorageListener = (key: string, callback: (value: string | null) => void) => {
  const handleStorageChange = (event: StorageEvent) => {
    if (event.key === key) {
      callback(event.newValue);
    }
  };

  onMounted(() => {
    window.addEventListener('storage', handleStorageChange);
  });

  onUnmounted(() => {
    window.removeEventListener('storage', handleStorageChange);
  });
};
  • 在 React 中,我们创建了一个自定义 Hook useLocalStorageListener,通过传入监听的 key 和回调函数来捕获 LocalStorage 的变化。
  • 在 Vue 中,我们创建了一个工具函数 useLocalStorageListener,同样通过传入 key 和回调函数来监听变化。

总结

在同一个浏览器页签中监听 localStorage 的变化并非难事,但不同场景下需要不同的方案。从简单的轮询到高级的 BroadcastChannel,本文介绍的几种方案各有优缺点。根据你的实际需求,选择合适的方案可以帮助你更高效地解决问题。

  • 简单需求:可以考虑使用自定义 StorageEventCustomEvent 实现监听。
  • 复杂需求:对于更高级的场景,如跨页面通信,MessageChannelBroadcastChannel 是更好的选择。

如果您觉得这篇文章有帮助,请点个赞吧~

分享文章

相关文章

更多文章 →
javascript2026-02-24
navigator.sendBeacon全指南
在前端开发中,埋点系统是必不可少的一环。我们经常需要在用户 关闭页面 、 刷新 或 跳转路由 时,向服务器发送最后一条统计数据(比如用户停留时长、页面跳出率)。 但这看似简单的需求,在实现时却危机四伏:请求发不出去?页面跳转卡顿?今天我们就来聊聊这个问题的终极解决方案 —— 。 一、 痛点与传统方案的挣扎 场景还原 当用户点击关闭按钮时,浏览器会触发生命周期事件( 或 )。如果我们直接使用普通的异步 AJAX ( 或 ) 发送请求,浏览...
学习
javascript2025-11-02
理解浏览器事件系统,从用户点击到事件对象的完整旅程
深入理解浏览器事件系统:从用户点击到事件对象的完整旅程 “当我点击页面按钮时,背后发生了什么?为什么回调函数能收到一个包含丰富信息的event对象?今天,让我们一起揭开浏览器事件系统的神秘面纱。” 一个令人困惑的现象 作为前端开发者,我们每天都在写这样的代码: 这段代码如此熟悉,以至于我们很少停下来思考:​ ​这个 对象到底从哪里来?它为什么能知道点击的精确坐标?为什么能识别是哪个元素被点击了?​ ​ 更神奇的是,当我们手动创建事件时:...
学习
javascript2025-10-01
实现大文件上传全流程详解
在日常开发中,大文件上传是个绕不开的坎——动辄几百 MB 甚至 GB 级的文件,直接上传不仅容易超时,还会让用户体验大打折扣。最近我用 Vue+Express 实现了一套完整的大文件上传方案,支持分片上传、断点续传、秒传和手动中。 一、先看效果:我们要实现什么? 先上核心功能清单,确保大家明确目标,知道我们要解决哪些实际问题: 大文件分片上传 :将文件切成固定大小的小片段分批上传,避免单次请求超时 秒传 :服务器已存在完整文件时,直接返...
学习
javascript2025-09-18
JavaScript 的多线程能力:Worker
如果你写过一些计算量稍大的 JavaScript 代码,比如图像处理、大量数据排序或者复杂的算法,你几乎肯定遇到过浏览器“卡死”的现象。点击页面没反应,动画也停了,就像整个世界都静止了。 这就是主线程被阻塞的典型后果。因为主线程既要负责执行 JavaScript,又要负责渲染页面、响应用户操作,一旦它被繁重的计算任务占满,就无暇顾及其他,用户体验便直线下降。 这个问题的根源,正是“主线程是单线程的”。那么,如何解决呢? 答案很简单:把这...
学习面试
javascript2025-09-15
一张 8K 海报差点把首屏拖垮
你给后台管理系统加了一个「企业风采」模块,运营同学一口气上传了 200 张 8K 宣传海报。首屏直接飙到 8.3 s,LCP 红得发紫。 老板一句「能不能像朋友圈那样滑到哪看到哪?」——于是你把懒加载重新翻出来折腾了一轮。 解决方案:三条技术路线,你全踩了一遍 1\. 最偷懒:原生 一行代码就能跑,浏览器帮你搞定。 🔍 关键决策点 2020 年后现代浏览器全覆盖,IE 全军覆没。 必须写死 ,否则 CLS 会抖成 PPT。 适用场景...
学习
javascript2025-09-10
🚀 Web Worker让你的应用丝滑
🌟 引言 在日常的前端开发中,你是否遇到过这样的困扰: 大数据处理时页面卡死 :处理几万条数据时,页面直接卡成PPT,用户点击毫无反应 复杂计算阻塞UI :图片处理、数据分析等计算密集型任务让整个应用假死 文件上传/下载卡顿 :大文件操作时,其他功能完全无法使用 实时数据处理性能差 :WebSocket接收大量数据时,页面渲染严重滞后 今天分享6个Web Worker的核心技巧,让你的应用告别卡顿,用户体验丝滑如德芙! 💡 核心技巧...
学习

评论

请登录后发表评论

去登录
加载评论中...

目录