突破限制:实现页面内精准监听 localStorage 变更

  • 一、简介
  • 二、示例演示
  • 三、StorageEvent重构setItem
  • 四、CustomEvent自定义事件同一页面不同模块数据同步
  • 五、MessageChannel同一页面不同模块数据同步
  • 六、BroadcastChannel多窗口数据同步
  • 七、CustomEvent+BroadcastChannel实现StorageEvent
  • 八、总结

一、简介

        localStorage 作为浏览器提供的本地存储 API,为开发者持久化保存客户端数据提供了便利。然而,它原生的事件监听机制存在一个关键限制:storage 事件仅在跨页面(同源的不同标签页或窗口)修改 localStorage 时触发,当前页面自身的修改则无法被监听到。这就形成了 “灯下黑” 现象,让开发者难以直接监测同一页面内的存储变更。
        虽然可以通过定时轮询来勉强实现监听,但这种方式问题颇多。它会持续消耗系统资源,犹如一个不知疲倦的 “消耗者”,而且存在监听延迟和遗漏风险,就像在捕捉快速移动的目标时,可能会错过关键瞬间。
        为解决这一核心痛点,业界已探索出多种高效解决方案。这些方法能够实现在页面内精准的实时监听。比如,利用 StorageEvent 监听 DOM 变化,间接判断 localStorage 的变动。当页面中的某些操作可能引发 localStorage 变化时,StorageEvent 能敏锐地察觉到,并及时做出响应。还有通过封装 localStorage 的操作方法,在每次修改时主动触发自定义事件,这种方式如同在 localStorage 的操作和监听之间建立了一座直接沟通的桥梁。这些解决方案为开发者提供了更多选择,让我们能够更灵活、高效地应对页面内 localStorage 变更的监听需求,为提升用户体验和应用程序的性能开辟了新的道路。它们就像是黑暗中的明灯,照亮了 localStorage 监听的 “灯下黑” 之处,让我们的开发工作更加顺畅。

二、示例演示

1.示例代码

<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>localStorage同页面监听问题</title><style>body {font-family: Arial, sans-serif;max-width: 800px;margin: 0 auto;padding: 20px;line-height: 1.6;}.container {display: flex;flex-direction: column;gap: 20px;}.box {border: 1px solid #ddd;padding: 15px;border-radius: 5px;}button {padding: 8px 16px;background-color: #4CAF50;color: white;border: none;border-radius: 4px;cursor: pointer;margin-right: 10px;}button:hover {background-color: #45a049;}.log {height: 150px;overflow-y: auto;border: 1px solid #ccc;padding: 10px;background-color: #f9f9f9;white-space: pre-wrap;}</style>
</head>
<body>
<div class="container"><h1>localStorage同页面监听问题演示</h1><div class="box"><h2>操作面板</h2><button id="setBtn">设置localStorage</button><button id="removeBtn">移除localStorage</button><button id="clearBtn">清空localStorage</button></div><div class="box"><h2>原生storage事件监听</h2><p>这个区域显示原生storage事件的触发情况</p><div id="nativeLog" class="log"></div></div><div class="box"><h2>同页面修改日志</h2><p>这个区域显示当前页面对localStorage的直接操作</p><div id="directLog" class="log"></div></div><div class="tips"><h3>问题说明:</h3><p>1. 点击上方按钮修改localStorage</p><p>2. 原生storage事件<strong>不会</strong>在同页面修改时触发</p><p>3. 只有<strong>其他同源页面</strong>修改localStorage时才会触发</p><p>4. 要测试原生事件,请打开另一个同源页面并修改localStorage</p></div>
</div><script>// 获取DOM元素const setBtn = document.getElementById('setBtn');const removeBtn = document.getElementById('removeBtn');const clearBtn = document.getElementById('clearBtn');const nativeLog = document.getElementById('nativeLog');const directLog = document.getElementById('directLog');// 原生storage事件监听window.addEventListener('storage', (event) => {const logEntry = `[原生事件] ${new Date().toLocaleTimeString()}Key: ${event.key}Old Value: ${event.oldValue}New Value: ${event.newValue}URL: ${event.url}------------------------------`;nativeLog.innerHTML += logEntry;nativeLog.scrollTop = nativeLog.scrollHeight;});// 设置localStoragesetBtn.addEventListener('click', () => {const key = 'testKey';const value = `Value-${Date.now()}`;const oldValue = localStorage.getItem(key);localStorage.setItem(key, value);const logEntry = `[直接操作] ${new Date().toLocaleTimeString()}设置了: ${key} = ${value}旧值: ${oldValue}------------------------------`;directLog.innerHTML += logEntry;directLog.scrollTop = directLog.scrollHeight;});// 移除localStorageremoveBtn.addEventListener('click', () => {const key = 'testKey';const oldValue = localStorage.getItem(key);localStorage.removeItem(key);const logEntry = `[直接操作] ${new Date().toLocaleTimeString()}移除了: ${key}旧值: ${oldValue}------------------------------`;directLog.innerHTML += logEntry;directLog.scrollTop = directLog.scrollHeight;});// 清空localStorageclearBtn.addEventListener('click', () => {localStorage.clear();const logEntry = `[直接操作] ${new Date().toLocaleTimeString()}清空了所有localStorage数据------------------------------`;directLog.innerHTML += logEntry;directLog.scrollTop = directLog.scrollHeight;});
</script>
</body>
</html>

2.将上述代码复制到test.html中,使用webstrom打开,点击设置localStorage,可以看到storage事件监听中并未触发
在这里插入图片描述
3.再将上述代码复制到test1.html中,使用webstrom打开,点击设置localStorage,可以看到此时的test.html已触发storage事件监听
在这里插入图片描述

三、StorageEvent重构setItem

StorageEvent 是 Web Storage API 的一部分,当存储区域(localStorage 或 sessionStorage)被其他文档修改时,会触发这个事件。
基本概念:

  • 跨文档通信:StorageEvent 主要用于在同一个源(origin)下的不同窗口或标签页之间通信
  • 触发条件:只有当其他窗口或标签页修改了存储时才会触发,当前窗口的修改不会触发
  • 监听方式:通过 window.addEventListener(‘storage’, callback) 监听

StorageEvent 对象包含以下重要属性:

  • key:被修改的键名。如果是调用 clear() 方法则为 null
  • oldValue:修改前的值。如果是新增项则为 null
  • newValue:修改后的值。如果是删除项则为 null
  • url:触发修改的文档的 URL
  • storageArea:被操作的存储对象(localStorage 或 sessionStorage)

1.在代码顶部加上下面图片中的代码
在这里插入图片描述
2.StorageEvent重构setItem代码

const originalSetItem = localStorage.setItem;
const originalRemoveItem = localStorage.removeItem;// 重写 setItem,在存储数据时触发自定义事件
localStorage.setItem = function(key, value) {const event = new StorageEvent('storage', {key,oldValue: localStorage.getItem(key),newValue: value,storageArea: localStorage,url: window.location.href});originalSetItem.call(localStorage, key, value); // 调用原始方法window.dispatchEvent(event); // 手动触发事件
};// 重写 removeItem,在移除数据时触发自定义事件
localStorage.removeItem = function(key) {const event = new StorageEvent('storage', {key,oldValue: localStorage.getItem(key),newValue: null,storageArea: localStorage,url: window.location.href});originalRemoveItem.call(localStorage, key); // 调用原始方法window.dispatchEvent(event); // 手动触发事件
};

3.实际效果
在这里插入图片描述

四、CustomEvent自定义事件同一页面不同模块数据同步

CustomEvent 是 JavaScript 中用于创建自定义事件的接口,继承自 Event,允许传递自定义数据(通过 detail 属性),常用于组件间通信或模拟原生事件。通过 new CustomEvent(type, options) 创建,options 可配置 bubbles(冒泡)、cancelable(可取消)及 detail(携带数据),随后通过 dispatchEvent 触发

<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>localStorage同页面监听问题</title><style>body {font-family: Arial, sans-serif;max-width: 800px;margin: 0 auto;padding: 20px;line-height: 1.6;}.container {display: flex;flex-direction: column;gap: 20px;}.box {border: 1px solid #ddd;padding: 15px;border-radius: 5px;}button {padding: 8px 16px;background-color: #4CAF50;color: white;border: none;border-radius: 4px;cursor: pointer;margin-right: 10px;}button:hover {background-color: #45a049;}.log {height: 150px;overflow-y: auto;border: 1px solid #ccc;padding: 10px;background-color: #f9f9f9;white-space: pre-wrap;}</style>
</head>
<body>
<div class="container"><h1>localStorage同页面监听问题演示</h1><div class="box"><h2>操作面板</h2><button id="setBtn">设置localStorage</button><button id="removeBtn">移除localStorage</button><button id="clearBtn">清空localStorage</button></div><div class="box"><h2>原生storage事件监听</h2><p>这个区域显示原生storage事件的触发情况</p><div id="nativeLog" class="log"></div></div><div class="box"><h2>同页面修改日志</h2><p>这个区域显示当前页面对localStorage的直接操作</p><div id="directLog" class="log"></div></div><div class="tips"><h3>问题说明:</h3><p>1. 点击上方按钮修改localStorage</p><p>2. 原生storage事件<strong>不会</strong>在同页面修改时触发</p><p>3. 只有<strong>其他同源页面</strong>修改localStorage时才会触发</p><p>4. 要测试原生事件,请打开另一个同源页面并修改localStorage</p></div>
</div><script>// 获取DOM元素const setBtn = document.getElementById('setBtn');const removeBtn = document.getElementById('removeBtn');const clearBtn = document.getElementById('clearBtn');const nativeLog = document.getElementById('nativeLog');const directLog = document.getElementById('directLog');const createLocalStorageEvent = (key, oldValue, newValue, type) => {return new CustomEvent('localStorageChange',{detail:{key,oldValue,newValue,type,timestamp: Date.now(),url: window.location.href}});};window.addEventListener('localStorageChange', (event) => {const logEntry = `[Proxy监听] ${new Date().toLocaleTimeString()}操作类型: ${event.detail.type}Key: ${event.detail.key}Old Value: ${event.detail.oldValue}New Value: ${event.detail.newValue}------------------------------`;nativeLog.innerHTML += logEntry;nativeLog.scrollTop = nativeLog.scrollHeight;});const originalSetItem = localStorage.setItem;const originalRemoveItem = localStorage.removeItem;// 重写 setItem,在存储数据时触发自定义事件localStorage.setItem = function(key, value) {originalSetItem.call(localStorage, key, value); // 调用原始方法window.dispatchEvent(createLocalStorageEvent(key, localStorage.getItem(key), value, 'setItem')); // 手动触发事件};// 重写 removeItem,在移除数据时触发自定义事件localStorage.removeItem = function(key) {originalRemoveItem.call(localStorage, key); // 调用原始方法window.dispatchEvent(createLocalStorageEvent(key, localStorage.getItem(key), null, 'removeItem')); // 手动触发事件};// 原生storage事件监听// window.addEventListener('storage', (event) => {//     const logEntry = `//         [原生事件] ${new Date().toLocaleTimeString()}//         Key: ${event.key}//         Old Value: ${event.oldValue}//         New Value: ${event.newValue}//         URL: ${event.url}//         ------------------------------//         `;//     nativeLog.innerHTML += logEntry;//     nativeLog.scrollTop = nativeLog.scrollHeight;// });// 设置localStoragesetBtn.addEventListener('click', () => {const key = 'testKey';const value = `Value-${Date.now()}`;const oldValue = localStorage.getItem(key);localStorage.setItem(key, value);const logEntry = `[直接操作] ${new Date().toLocaleTimeString()}设置了: ${key} = ${value}旧值: ${oldValue}------------------------------`;directLog.innerHTML += logEntry;directLog.scrollTop = directLog.scrollHeight;});// 移除localStorageremoveBtn.addEventListener('click', () => {const key = 'testKey';const oldValue = localStorage.getItem(key);localStorage.removeItem(key);const logEntry = `[直接操作] ${new Date().toLocaleTimeString()}移除了: ${key}旧值: ${oldValue}------------------------------`;directLog.innerHTML += logEntry;directLog.scrollTop = directLog.scrollHeight;});// 清空localStorageclearBtn.addEventListener('click', () => {localStorage.clear();const logEntry = `[直接操作] ${new Date().toLocaleTimeString()}清空了所有localStorage数据------------------------------`;![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/3087bfff6e4b4feebaf9d6456ec59d9a.gif#pic_center)directLog.innerHTML += logEntry;directLog.scrollTop = directLog.scrollHeight;});
</script>
</body>
</html>

在这里插入图片描述

五、MessageChannel同一页面不同模块数据同步

MessageChannel 是浏览器提供的双向通信管道,创建一对相互关联的 MessagePort(port1 和 port2),允许不同上下文(如窗口、Worker、iframe)直接传输数据,实现点对点高效通信,无需依赖广播或全局事件。通过 postMessage 发送消息,onmessage 接收,适用于跨线程任务分发或框架内部通信,性能优于 postMessage 的全局通信。

<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>localStorage同页面监听问题</title><style>body {font-family: Arial, sans-serif;max-width: 800px;margin: 0 auto;padding: 20px;line-height: 1.6;}.container {display: flex;flex-direction: column;gap: 20px;}.box {border: 1px solid #ddd;padding: 15px;border-radius: 5px;}button {padding: 8px 16px;background-color: #4CAF50;color: white;border: none;border-radius: 4px;cursor: pointer;margin-right: 10px;}button:hover {background-color: #45a049;}.log {height: 150px;overflow-y: auto;border: 1px solid #ccc;padding: 10px;background-color: #f9f9f9;white-space: pre-wrap;}</style>
</head>
<body>
<div class="container"><h1>localStorage同页面监听问题演示</h1><div class="box"><h2>操作面板</h2><button id="setBtn">设置localStorage</button><button id="removeBtn">移除localStorage</button><button id="clearBtn">清空localStorage</button></div><div class="box"><h2>原生storage事件监听</h2><p>这个区域显示原生storage事件的触发情况</p><div id="nativeLog" class="log"></div></div><div class="box"><h2>同页面修改日志</h2><p>这个区域显示当前页面对localStorage的直接操作</p><div id="directLog" class="log"></div></div><div class="tips"><h3>问题说明:</h3><p>1. 点击上方按钮修改localStorage</p><p>2. 原生storage事件<strong>不会</strong>在同页面修改时触发</p><p>3. 只有<strong>其他同源页面</strong>修改localStorage时才会触发</p><p>4. 要测试原生事件,请打开另一个同源页面并修改localStorage</p></div>
</div><script>// 获取DOM元素const setBtn = document.getElementById('setBtn');const removeBtn = document.getElementById('removeBtn');const clearBtn = document.getElementById('clearBtn');const nativeLog = document.getElementById('nativeLog');const directLog = document.getElementById('directLog');// 创建 MessageChannelconst channel = new MessageChannel();const port1 = channel.port1;const port2 = channel.port2;// 存储原始方法const originalSetItem = localStorage.setItem;const originalRemoveItem = localStorage.removeItem;const originalClear = localStorage.clear;// 重写 localStorage 方法localStorage.setItem = function(key, value) {const oldValue = localStorage.getItem(key);originalSetItem.call(localStorage, key, value);// 通过 MessageChannel 发送消息port1.postMessage({type: 'storage',key,oldValue,newValue: value,action: 'set'});};localStorage.removeItem = function(key) {const oldValue = localStorage.getItem(key);originalRemoveItem.call(localStorage, key);port1.postMessage({type: 'storage',key,oldValue,newValue: null,action: 'remove'});};localStorage.clear = function() {originalClear.call(localStorage);port1.postMessage({type: 'storage',action: 'clear'});};// 监听 MessageChannel 消息port2.onmessage = function(event) {const { type, key, oldValue, newValue, action } = event.data;if (type === 'storage') {let message = '';switch (action) {case 'set':message = `Key "${key}" 被修改: ${oldValue}${newValue}`;break;case 'remove':message = `Key "${key}" 被移除 (旧值: ${oldValue})`;break;case 'clear':message = 'localStorage 已清空';break;}const logEntry = `[原生事件] ${new Date().toLocaleTimeString()}Key: ${event.key}Old Value: ${event.oldValue}New Value: ${event.newValue}URL: ${event.url}message : ${message}------------------------------`;nativeLog.innerHTML += logEntry;nativeLog.scrollTop = nativeLog.scrollHeight;}};// 启动端口监听port2.start();// // 原生storage事件监听// window.addEventListener('storage', (event) => {//     const logEntry = `//         [原生事件] ${new Date().toLocaleTimeString()}//         Key: ${event.key}//         Old Value: ${event.oldValue}//         New Value: ${event.newValue}//         URL: ${event.url}//         ------------------------------//         `;//     nativeLog.innerHTML += logEntry;//     nativeLog.scrollTop = nativeLog.scrollHeight;// });// 设置localStoragesetBtn.addEventListener('click', () => {const key = 'testKey';const value = `Value-${Date.now()}`;const oldValue = localStorage.getItem(key);localStorage.setItem(key, value);const logEntry = `[直接操作] ${new Date().toLocaleTimeString()}设置了: ${key} = ${value}旧值: ${oldValue}------------------------------`;directLog.innerHTML += logEntry;directLog.scrollTop = directLog.scrollHeight;});// 移除localStorageremoveBtn.addEventListener('click', () => {const key = 'testKey';const oldValue = localStorage.getItem(key);localStorage.removeItem(key);const logEntry = `[直接操作] ${new Date().toLocaleTimeString()}移除了: ${key}旧值: ${oldValue}------------------------------`;directLog.innerHTML += logEntry;directLog.scrollTop = directLog.scrollHeight;});// 清空localStorageclearBtn.addEventListener('click', () => {localStorage.clear();const logEntry = `[直接操作] ${new Date().toLocaleTimeString()}清空了所有localStorage数据------------------------------`;directLog.innerHTML += logEntry;directLog.scrollTop = directLog.scrollHeight;});
</script>
</body>
</html>

在这里插入图片描述

六、BroadcastChannel多窗口数据同步

BroadcastChannel 是浏览器提供的跨页面通信API,允许同源下的不同窗口、标签页、iframe或Worker通过共享频道名实时广播和接收消息(如postMessage发送、onmessage监听),适用于多页数据同步(如登录状态、实时通知),无需依赖服务端或局部存储,通信高效且自动管理连接,关闭页面或调用close()即可释放资源

<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>localStorage同页面监听问题</title><style>body {font-family: Arial, sans-serif;max-width: 800px;margin: 0 auto;padding: 20px;line-height: 1.6;}.container {display: flex;flex-direction: column;gap: 20px;}.box {border: 1px solid #ddd;padding: 15px;border-radius: 5px;}button {padding: 8px 16px;background-color: #4CAF50;color: white;border: none;border-radius: 4px;cursor: pointer;margin-right: 10px;}button:hover {background-color: #45a049;}.log {height: 150px;overflow-y: auto;border: 1px solid #ccc;padding: 10px;background-color: #f9f9f9;white-space: pre-wrap;}</style>
</head>
<body>
<div class="container"><h1>localStorage同页面监听问题演示</h1><div class="box"><h2>操作面板</h2><button id="setBtn">设置localStorage</button><button id="removeBtn">移除localStorage</button><button id="clearBtn">清空localStorage</button></div><div class="box"><h2>原生storage事件监听</h2><p>这个区域显示原生storage事件的触发情况</p><div id="nativeLog" class="log"></div></div><div class="box"><h2>同页面修改日志</h2><p>这个区域显示当前页面对localStorage的直接操作</p><div id="directLog" class="log"></div></div><div class="tips"><h3>问题说明:</h3><p>1. 点击上方按钮修改localStorage</p><p>2. 原生storage事件<strong>不会</strong>在同页面修改时触发</p><p>3. 只有<strong>其他同源页面</strong>修改localStorage时才会触发</p><p>4. 要测试原生事件,请打开另一个同源页面并修改localStorage</p></div>
</div><script>// 获取DOM元素const setBtn = document.getElementById('setBtn');const removeBtn = document.getElementById('removeBtn');const clearBtn = document.getElementById('clearBtn');const nativeLog = document.getElementById('nativeLog');const directLog = document.getElementById('directLog');// 创建 BroadcastChannelconst storageChannel = new BroadcastChannel('local_storage_channel');// 存储原始方法const originalSetItem = localStorage.setItem;const originalRemoveItem = localStorage.removeItem;const originalClear = localStorage.clear;// 重写 localStorage 方法localStorage.setItem = function(key, value) {const oldValue = localStorage.getItem(key);originalSetItem.call(localStorage, key, value);// 通过 BroadcastChannel 广播消息storageChannel.postMessage({type: 'storage',key,oldValue,newValue: value,action: 'set'});};localStorage.removeItem = function(key) {const oldValue = localStorage.getItem(key);originalRemoveItem.call(localStorage, key);storageChannel.postMessage({type: 'storage',key,oldValue,newValue: null,action: 'remove'});};localStorage.clear = function() {originalClear.call(localStorage);storageChannel.postMessage({type: 'storage',action: 'clear'});};// 监听 BroadcastChannel 消息storageChannel.onmessage = function(event) {const { type, key, oldValue, newValue, action } = event.data;if (type === 'storage') {let message = '';switch (action) {case 'set':message = `Key "${key}" 被修改: ${oldValue}${newValue}`;break;case 'remove':message = `Key "${key}" 被移除 (旧值: ${oldValue})`;break;case 'clear':message = 'localStorage 已清空';break;}const logEntry = `[原生事件] ${new Date().toLocaleTimeString()}Key: ${event.key}Old Value: ${event.oldValue}New Value: ${event.newValue}URL: ${event.url}message : ${message}------------------------------`;nativeLog.innerHTML += logEntry;nativeLog.scrollTop = nativeLog.scrollHeight;}};// 原生storage事件监听// window.addEventListener('storage', (event) => {//     const logEntry = `//         [原生事件] ${new Date().toLocaleTimeString()}//         Key: ${event.key}//         Old Value: ${event.oldValue}//         New Value: ${event.newValue}//         URL: ${event.url}//         ------------------------------//         `;//     nativeLog.innerHTML += logEntry;//     nativeLog.scrollTop = nativeLog.scrollHeight;// });// 设置localStoragesetBtn.addEventListener('click', () => {const key = 'testKey';const value = `Value-${Date.now()}`;const oldValue = localStorage.getItem(key);localStorage.setItem(key, value);const logEntry = `[直接操作] ${new Date().toLocaleTimeString()}设置了: ${key} = ${value}旧值: ${oldValue}------------------------------`;directLog.innerHTML += logEntry;directLog.scrollTop = directLog.scrollHeight;});// 移除localStorageremoveBtn.addEventListener('click', () => {const key = 'testKey';const oldValue = localStorage.getItem(key);localStorage.removeItem(key);const logEntry = `[直接操作] ${new Date().toLocaleTimeString()}移除了: ${key}旧值: ${oldValue}------------------------------`;directLog.innerHTML += logEntry;directLog.scrollTop = directLog.scrollHeight;});// 清空localStorageclearBtn.addEventListener('click', () => {localStorage.clear();const logEntry = `[直接操作] ${new Date().toLocaleTimeString()}清空了所有localStorage数据------------------------------`;directLog.innerHTML += logEntry;directLog.scrollTop = directLog.scrollHeight;});
</script>
</body>
</html>

在这里插入图片描述

七、CustomEvent+BroadcastChannel实现StorageEvent

通过代码分析可以发现,原生CustomEvent和MessageChannel机制无法实现跨页面监听storage变更,而BroadcastChannel虽然支持跨页面通信但在单页面内的多模块协同场景中存在局限性。为构建更完善的监听方案,可采用CustomEvent与BroadcastChannel相结合的方式:利用CustomEvent实现单页面内的高效精准监听,同时通过BroadcastChannel确保跨页面的实时同步,从而打造一个既能满足同域多页面数据同步需求,又能完美支持单页面多模块通信的增强型StorageEvent监听体系。

<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>localStorage同页面监听问题</title><style>body {font-family: Arial, sans-serif;max-width: 800px;margin: 0 auto;padding: 20px;line-height: 1.6;}.container {display: flex;flex-direction: column;gap: 20px;}.box {border: 1px solid #ddd;padding: 15px;border-radius: 5px;}button {padding: 8px 16px;background-color: #4CAF50;color: white;border: none;border-radius: 4px;cursor: pointer;margin-right: 10px;}button:hover {background-color: #45a049;}.log {height: 150px;overflow-y: auto;border: 1px solid #ccc;padding: 10px;background-color: #f9f9f9;white-space: pre-wrap;}</style>
</head>
<body>
<div class="container"><h1>localStorage同页面监听问题演示</h1><div class="box"><h2>操作面板</h2><button id="setBtn">设置localStorage</button><button id="removeBtn">移除localStorage</button><button id="clearBtn">清空localStorage</button></div><div class="box"><h2>原生storage事件监听</h2><p>这个区域显示原生storage事件的触发情况</p><div id="nativeLog" class="log"></div></div><div class="box"><h2>同页面修改日志</h2><p>这个区域显示当前页面对localStorage的直接操作</p><div id="directLog" class="log"></div></div><div class="tips"><h3>问题说明:</h3><p>1. 点击上方按钮修改localStorage</p><p>2. 原生storage事件<strong>不会</strong>在同页面修改时触发</p><p>3. 只有<strong>其他同源页面</strong>修改localStorage时才会触发</p><p>4. 要测试原生事件,请打开另一个同源页面并修改localStorage</p></div>
</div><script>// 获取DOM元素const setBtn = document.getElementById('setBtn');const removeBtn = document.getElementById('removeBtn');const clearBtn = document.getElementById('clearBtn');const nativeLog = document.getElementById('nativeLog');const directLog = document.getElementById('directLog');const handleStorageChange = (info) => {const logEntry = `[Proxy监听] ${new Date().toLocaleTimeString()}操作类型: ${info.type}Key: ${info.key}Old Value: ${info.oldValue}New Value: ${info.newValue}Message: ${info.message}------------------------------`;nativeLog.innerHTML += logEntry;nativeLog.scrollTop = nativeLog.scrollHeight;}const createLocalStorageEvent = (key, oldValue, newValue, type, message) => {return new CustomEvent('localStorageChange',{detail:{key,oldValue,newValue,type,timestamp: Date.now(),url: window.location.href,message: message}});};window.addEventListener('localStorageChange', (event) => {handleStorageChange(event.detail);});// 创建 BroadcastChannelconst storageChannel = new BroadcastChannel('local_storage_channel');const originalSetItem = localStorage.setItem;const originalRemoveItem = localStorage.removeItem;// 重写 setItem,在存储数据时触发自定义事件localStorage.setItem = function(key, value) {const oldValue = localStorage.getItem(key);originalSetItem.call(localStorage, key, value); // 调用原始方法window.dispatchEvent(createLocalStorageEvent(key, oldValue, value, 'setItem',`Key "${key}" 被修改: ${oldValue}${value}`)); // 手动触发事件// 通过 BroadcastChannel 广播消息storageChannel.postMessage({type: 'storage',key,oldValue,newValue: value,action: 'set'});};// 重写 removeItem,在移除数据时触发自定义事件localStorage.removeItem = function(key) {const oldValue = localStorage.getItem(key);originalRemoveItem.call(localStorage, key); // 调用原始方法window.dispatchEvent(createLocalStorageEvent(key, oldValue, null, 'removeItem',`Key "${key}" 被移除 (旧值: ${oldValue})`)); // 手动触发事件storageChannel.postMessage({type: 'storage',key,oldValue,newValue: null,action: 'remove'});};// 监听 BroadcastChannel 消息storageChannel.onmessage = function(event) {const { type, key, oldValue, newValue, action } = event.data;if (type === 'storage') {let message = '';switch (action) {case 'set':message = `Key "${key}" 被修改: ${oldValue}${newValue}`;break;case 'remove':message = `Key "${key}" 被移除 (旧值: ${oldValue})`;break;case 'clear':message = 'localStorage 已清空';break;}handleStorageChange({...event,message: message,});}};// 原生storage事件监听// window.addEventListener('storage', (event) => {//     const logEntry = `//         [原生事件] ${new Date().toLocaleTimeString()}//         Key: ${event.key}//         Old Value: ${event.oldValue}//         New Value: ${event.newValue}//         URL: ${event.url}//         ------------------------------//         `;//     nativeLog.innerHTML += logEntry;//     nativeLog.scrollTop = nativeLog.scrollHeight;// });// 设置localStoragesetBtn.addEventListener('click', () => {const key = 'testKey';const value = `Value-${Date.now()}`;const oldValue = localStorage.getItem(key);localStorage.setItem(key, value);const logEntry = `[直接操作] ${new Date().toLocaleTimeString()}设置了: ${key} = ${value}旧值: ${oldValue}------------------------------`;directLog.innerHTML += logEntry;directLog.scrollTop = directLog.scrollHeight;});// 移除localStorageremoveBtn.addEventListener('click', () => {const key = 'testKey';const oldValue = localStorage.getItem(key);localStorage.removeItem(key);const logEntry = `[直接操作] ${new Date().toLocaleTimeString()}移除了: ${key}旧值: ${oldValue}------------------------------`;directLog.innerHTML += logEntry;directLog.scrollTop = directLog.scrollHeight;});// 清空localStorageclearBtn.addEventListener('click', () => {localStorage.clear();const logEntry = `[直接操作] ${new Date().toLocaleTimeString()}清空了所有localStorage数据------------------------------`;directLog.innerHTML += logEntry;directLog.scrollTop = directLog.scrollHeight;});
</script>
</body>
</html>

在这里插入图片描述
在这里插入图片描述

八、总结

1.精确监听localStorage变化:在同一页面内需要精准监控存储变更时,推荐采用StorageEvent或CustomEvent方案,两者都具有简洁明了的实现方式。
2.系统扩展需求:当项目需要构建高度可定制的事件系统时,CustomEvent凭借其灵活的自定义能力成为首选,可以完美支持各种特殊操作需求。
3.模块间高频通信:对于页面内不同组件间需要持续、高效的数据交换场景,MessageChannel的点对点通信机制能提供最优的性能保障。
4.跨窗口协作:在多窗口或多标签页需要保持数据同步的工作场景下,BroadcastChannel的广播特性是最有效的解决方案。
5.跨页面自定义存储事件:若需要实现跨页面且可定制的存储监听系统,可采用 CustomEvent与BroadcastChannel结合 的方案——通过BroadcastChannel广播存储变更消息,各页面用CustomEvent触发本地监听,从而兼顾跨页面通信与灵活的事件处理能力。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/86340.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/86340.shtml
英文地址,请注明出处:http://en.pswp.cn/pingmian/86340.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

牛客AI面试破解电销招聘效率与成本双重难题

在电销行业&#xff0c;高流动性与大规模招聘需求长期困扰企业人力资源管理。传统招聘模式下&#xff0c;HR需应对海量简历筛选、多轮面试协调、主观评估偏差等挑战&#xff0c;导致招聘周期长、成本高、人才匹配度低。如何通过技术手段实现精准筛选与效率提升&#xff1f;牛客…

智慧生产管控数字化平台(源码+文档+讲解+演示)

引言 在全球化和信息化的浪潮中&#xff0c;制造业正面临着前所未有的挑战和机遇。智慧生产管控数字化平台应运而生&#xff0c;旨在通过数字化手段优化生产管控的全流程。本文将详细介绍智慧生产管控数字化平台的核心功能、技术架构以及如何通过开源代码实现二次开发&#xf…

用Tensorflow进行线性回归和逻辑回归(九)

用TensorFlow训练线性和逻辑回归模型 这一节结合前面介绍的所有TensorFlow概念来训练线性和逻辑回归模型&#xff0c;使用玩具数据集。 用TensorFlow训练模型 假如我们指明了数据点和标签的容器&#xff0c;定义了张量操作的损失函数。添加了优化器节点到计算图&#xff0c;…

使用 vue vxe-table 实现复选框禁用,根据行规则来禁用是否允许被勾选选中

使用 vue vxe-table 实现复选框禁用&#xff0c;根据行规则来禁用是否允许被勾选选中 查看官网&#xff1a;https://vxetable.cn 禁用选中 通过 checkMethod 方法控制 checkbox 是否允许用户手动勾选&#xff0c;如果被禁用&#xff0c;可以调用 setCheckboxRow 方法手动设置…

【Linux-网络】深入拆解TCP核心机制与UDP的无状态设计

&#x1f3ac; 个人主页&#xff1a;谁在夜里看海. &#x1f4d6; 个人专栏&#xff1a;《C系列》《Linux系列》《算法系列》 ⛰️ 道阻且长&#xff0c;行则将至 目录 &#x1f4da;引言 &#x1f4da;一、UDP协议 &#x1f4d6; 1.概述 &#x1f4d6; 2.特点 &#x1…

(nice!!!)(LeetCode 每日一题) 2081. k 镜像数字的和 (枚举)

题目&#xff1a;2081. k 镜像数字的和 思路&#xff1a;枚举10进制的回文串&#xff0c;然后来判断对应的k进制数是否是回文串。直到有n个满意即可。 而枚举10进制的回文串&#xff0c;从基数p(1、10、100… )开始&#xff0c;长度为奇数的回文串&#xff0c;长度为偶数的回文…

Java面试题027:一文深入了解数据库Redis(3)

Java面试题025&#xff1a;一文深入了解数据库Redis&#xff08;1&#xff09; Java面试题026&#xff1a;一文深入了解数据库Redis&#xff08;2&#xff09; 本节我们整理一下Redis高可用和消息队列使用场景的重点原理&#xff0c;让大家在面试或者实际工作中遇到这类问题时…

算法打卡 day4

4 . 高精度算法 性质&#xff1a;数组或者容器从低位往高位依次存储大整数&#xff0c;方便进位。 4.1 高精度加法 给定两个正整数&#xff08;不含前导 0&#xff09;&#xff0c;计算它们的和。 输入格式 共两行&#xff0c;每行包含一个整数。 输出格式 共一行&#xff0c;…

【笔记】Docker 配置阿里云镜像加速(公共地址即开即用,无需手动创建实例)

2025年06月25日记 【好用但慎用】Windows 系统中将所有 WSL 发行版从 C 盘迁移到 非系统 盘的完整笔记&#xff08;附 异常处理&#xff09;-CSDN博客 【笔记】解决 WSL 迁移后 Docker 出现 “starting services: initializing Docker API Proxy: setting up docker ap” 问题…

day35-Django(1)

day35-Django 3.2 前言 之前我们介绍过web应用程序和http协议,简单了解过web开发的概念。Web应用程序的本质 接收并解析HTTP请求,获取具体的请求信息处理本次HTTP请求,即完成本次请求的业务逻辑处理构造并返回处理结果——HTTP响应import socketserver = socket.socket() …

PostgreSQL全栈部署指南:从零构建企业级高可用数据库集群

PostgreSQL全栈部署指南:从零构建企业级数据库集群 前言: 本文详解了**PostgreSQL**所有的部署方式,如 yum 安装、源码编译安装、RPM包手动安装,以及如何选择适合的安装方式。适合不同的场景应用。通过高可用部署详细了解安装思路及过程,包括内网环境下的配置、主节点的创…

MQTT 和 HTTP 有什么本质区别?

MQTT 和 HTTP 的本质区别在于它们设计的初衷和核心工作模式完全不同。它们是为解决不同问题而创造的两种工具。 简单来说&#xff1a; HTTP 就像是去图书馆问问题&#xff1a;你&#xff08;客户端&#xff09;主动去找图书管理员&#xff08;服务器&#xff09;&#xff0c;…

GtkSharp跨平台WinForm实现

文章目录 跨平台架构设计跨平台项目配置GtkSharp串口通讯实现跨平台部署配置Linux系统配置macOS系统配置 相关学习资源GTK#跨平台开发跨平台.NET开发Linux开发环境macOS开发环境跨平台UI框架对比容器化部署开源项目参考性能优化与调试 跨平台架构设计 基于GTKSystem.Windows.F…

【闲谈】对于c++未来的看法

对于C未来看法 C 作为一门诞生于上世纪的编程语言&#xff0c;在软件工业发展史上扮演了不可替代的角色。尽管近年来诸如 Rust、Go、Swift、Kotlin 等现代语言相继崛起&#xff0c;C 依然在系统软件、高性能服务、嵌入式等关键领域中发挥着主力作用。本文将从 C 的当前应用前景…

【论文】云原生事件驱动架构在智能风控系统中的实践与思考

摘要 2023年6月至2024年3月,我作为某头部证券公司新一代极速交易系统的首席架构师,主导设计并落地了基于云原生事件驱动架构的全新交易风控平台。该项目旨在攻克原有系统无法支撑峰值20万笔/秒交易量、风控延迟超过3秒以及行情剧烈波动时系统崩溃等核心痛点。通过构建以Kube…

opensbi从0到1入门学习

最近要在RV64的平台上把Linux给bringup起来&#xff0c;由于当下的工作主要集中在底层硬件接口驱动、CPU的操作及RTOS应用等&#xff0c;虽然之前搞过Arm Linux的开发工作&#xff0c;但是比较基础的玩的比较少&#xff0c;所以真正要搞把系统bringup起来&#xff0c;我之前的知…

Python打卡:Day36

复习日 浙大疏锦行

开发过程中的时空权衡:如何优雅地平衡时间与空间效率

文章目录 恒的开发者困境一、理解时间与空间的基本概念1. 时间复杂度2. 空间复杂度 二、时空权衡的基本原则1. 硬件环境决定优先级2. 应用场景决定策略3. 数据规模的影响 三、实际开发中的权衡策略1. 缓存为王&#xff1a;用空间换时间2. 压缩数据&#xff1a;用时间换空间3. 预…

RAG 应用实战指南:从商业目标到系统落地与运营 E2E 实践

专栏入口 前言 在当今信息爆炸的时代&#xff0c;如何高效地从海量数据中提取有用信息并提供智能问答服务&#xff0c;成为众多企业关注的焦点。检索增强生成&#xff08;Retrieval-Augmented Generation, RAG&#xff09;技术以其结合了检索模型的精准性和生成模型的灵活性&a…

关于晨脉的概念解释

晨脉&#xff08;Resting Morning Pulse&#xff09;是指​​人体在清晨清醒后、未进行任何活动前​​&#xff0c;于卧床状态下测量的每分钟脉搏或心率次数。它反映了人体在无运动消耗、无神经干扰时的基础代谢状态&#xff0c;是评估心脏功能、身体恢复情况及运动适应性的重要…