先记住这个答案
因为 storage 事件的设计意图是让同一存储区的其他文档感知存储变化,当前页面自己改动后本身就知道新值,无需再被通知,若也触发反而容易造成冗余处理或循环写入。通知范围必须区分存储类型:localStorage 事件会通知同源的其他标签页和 iframe;sessionStorage 事件只通知同源且同属一个顶级浏览上下文的其他文档(如页面内的 iframe),不会跨标签页。
storage事件只通知其他共享存储区的文档- 当前页面修改后不触发自身事件
- 同源不同标签页可收到 localStorage 事件
事件模型:由存储区广播给“其他”文档
规范规定,当某个文档调用 localStorage.setItem() 或 removeItem() 后,浏览器会遍历所有与该文档同源、且不属于该文档自身的“可访问该存储区的其他文档”,并向它们派发 storage 事件。改动文档本身被排除在接收者之外,因此不会进入自己的事件监听器。
这也意味着,storage 事件不是像 DOMContentLoaded 那样向上冒泡的 DOM 事件,而是基于存储区域变更触发的通知。它依赖浏览器内部的存储变更广播机制。即使其他文档与改动文档在同一个标签页内(如 iframe),只要满足同源且共享存储区,也会收到事件。
账单应用中两个标签页的同步问题
假设一个账单应用:标签页 A 和 B 都打开同一页面,用户可以在任一页添加新账单,代码用 localStorage 保存账单列表。如果页面上有代码监听 storage 事件并自动刷新列表,那么 A 修改时 B 会收到事件并更新,但 A 自身不会触发,这样 A 中的列表也必须立即用写入后的值主动更新 UI。
一个实际的例子:A 中用户新增一条,代码执行 setItem 后,A 需要直接调用本地渲染函数更新视图;而 B 通过事件回调重新读取 localStorage。如果 A 也依赖事件,就会错过自己的更新,导致界面不显示新账单。正确做法是自己主动更新,对事件只做旁路同步。
边界条件:清除、覆盖和同值写入
clear() 会使存储清空并触发事件,此时 event.key、event.oldValue、event.newValue 均为 null;removeItem() 只有在该键原本存在时才触发,且 event.key 为被移除的键名,event.oldValue 为原值,event.newValue 为 null。若写入的新值与旧值完全相等,规范不要求派发事件,因此 A 重复写入相同键值时 B 不会收到通知。此外,若两个文档实际上位于不同存储分区(比如浏览器对第三方 iframe 启用分区存储),即使同源,也可能不会共享 localStorage,事件自然也不会跨分区送达。
实现精确同步时可结合 BroadcastChannel,或在事件处理器中验证是否确实存在期望的新值。
容易答错的地方
- 误以为事件会冒泡到自身
- 有人认为
storage事件也像普通事件一样在窗口间传播,并试图通过捕获方式让自己也监听,但规范明确只给其他文档派发,当前窗口不会收到。 - 认为 sessionStorage 可跨标签页
- sessionStorage 的事件只在同一顶级浏览上下文的其他文档(如同页iframe)触发,不同标签页即使同源也不会收到,因为 sessionStorage 是标签页级别的。
面试官还会怎么问?
如何在同一页面内监听其他 iframe 的 localStorage 变化?
需保证 iframe 与主页面同源,且 iframe 修改 localStorage 时,主页面(另一文档)会收到 storage 事件;反之亦然。只要存储区共享且文档不同即可。
storage 事件触发时 event.storageArea 指向什么?
它指向当前 web 存储区实例,即该文档看到的 localStorage 或 sessionStorage。所以可通过 event.storageArea 读取最新值,但建议直接读取 window.localStorage。
如果 setItem 设置相同值,会触发事件吗?
不会。只有当值真正变化时才触发。若要强制通知,可先 removeItem 再 setItem,原键存在时会先触发一个移除事件,再触发一个添加事件,共两个事件;若原键不存在则只触发一个事件。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。