# 第一种:storageEvent
其实Javascript原生就有一个监听localStoarge 变化的事件--storage,使用方法如下
window.addEventListener('storage', () => {
// callback
})
我们来看看MDN是怎么描述这个事件的
TIP
StorageEvent 当前页面使用的storage被其他页面修改时会触发StorageEvent事件,
事件在同一个域下的不同页面之间触发,即在A页面注册了storage的监听事件,只有在跟A同域名下的B页面操作storage对象,A页面才会被处罚storage事件
也就是说,同域下的不同页面A、B,只有本页面修改了localStorage才会触发对方的storage事件
**但是显然这种方案很不适用在现在的大部分项目中,毕竟这种方案太局限了,不能应用在本页面监听的场景
# 传统方案的痛点
# 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. 监听代理(Prosy)或发布-订阅模式
这种方式通过创建一个代理来拦击 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,维护成本高(但也是值得推荐)
然而,这些方案往往存在性能问题或者开发的复杂度,在高频数据更新的情况下,有一定的性能问题,而且存在一定的风险性。那么有没有可以简单快速,风险性还小的方案呢?
# 封装localStroage--另
其实就是代理一下对localStorage 进行多一层的封装,使得我们每次在操作localStorage的时候,都会多走一层函数,而我们就可以在这一层中执行监听的事件了,下面是简单的代码例子
class CommonLocalStorage {
private storage:Storage;
constructor() {
this.storage = window.localStorage;
}
set(key: string, value: any) {
// 执行监听操作
return this.storage.setItem(`${prefix}${key}`, value);
}
get(key: string) {
// 执行监听操作
return this.storage.getItem(`${prefix}${key}`);
}
del(key: string) {
// 执行监听操作
return this.storage.removeItem(`${prefix}${key}`);
}
clear() {
// 执行监听的操作
this.storage.clear();
}
}
const commonStorage = new CommonLocalStorage();
export default commonStorage;
这种方式也被应用于很多比较出名的项目中,大家可以去看那些开源的项目中,基本很少基本直接使用localStorage,而是都会封装一层的
# 高效方案
既然浏览器不支持同一页签的 storage 事件,我们可以手动触发事件,以此来实现同一页签下的 LocalStorage 变化监听
# 1. 自定义 Storage 事件
通过手动触发 StorageEvent,你可以在LocalStorage更新时同步分发事件,从而实现同一页签下的监听
localStorage.setItem('myKey', 'value');
// 手动创建并分发 StorageEvent
const storageEvent = new StorageEvent('storage', {
key: 'myKey',
url: window.location.href
})
window.dispatchEvent(storageEvent);
你可以使用相同的监听逻辑来处理数据变化,无论是同一页签还是不同页签
window.addEventListener('storage', event => {
if(event.key === 'myKey') {
// 处理LocalStorage更新
}
})
这种实现简单、轻量、快捷。但是需要手动触发事件
# 1. 基于 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);
}
});
# MessageChannel(消息通道)
MessageChannel API可以在同一个浏览器上下文中发送和接收消息。我们可以通过MessageChannel将localStorage的变化信息同步到其他部分,起到类似事件监听效果
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'));
适合组件通信和复杂应用场景,消息机制较为灵活。相对复杂的实现,可能不适合简单场景。
# 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' });
# 计算LocalStorage容量
localStorage的容量大家都知道是5M,但是很少人知道怎么去验证,而且某些场景需要计算localstorage的剩余容量时,就需要掌握计算容量的技能了
# 计算总容量
我们以10KB一个单位,也就是10240B,1024B就是10240个字节的大小,我们不断往localStorage中累加存入10KB,等到超出最大存储时,会报错,那个时统计出所有累积的大小,就是总存储量了!
注意:计算前需要先清空 LocalStorage
let str = '0123456789';
let temp = '';
// 先做一个10KB的字符串
while(str.length !== 10240) {
str = str + '0123456789'
}
// 先清空
localStorage.clear();
const computedTotal = () => {
return new Promise((resolve) => {
// 不断往 LocalStorage 中累积存储 10KB
const timer = setInterval(() => {
try {
localStorage.setItem('temp', temp);
} catch {
// 报错说明超出最大存储
resolve(temp.length / 1024 - 10);
clearInterval(timer);
// 统计完记得清空
localStorage.clear();
}
}, 0)
})
}
(async () => {
const total = await computedTotal();
console.log(`最大容量${total}KB`)
})()
最后得出的最大存储量为5120KB ≈ 5M
# 已使用容量
计算已使用容量,我们只需要遍历 localStorage 身上的存储属性,并计算每一个length,累加起来就是已使用的容量了。
const computedUse = () => {
let cache = 0;
for(let key in localStorage) {
if(localStorage.hasOwnProperty(key)) {
cache += localStorage.getItem(key).length;
}
}
return (cache / 1024).toFixed(2)
}
(async () => {
const total = await computedTotal();
let o = '0123456789';
for(let i = 0; i < 1000; i++) {
o += '0123456789'
}
localStorage.setItem('o', o);
const useCache = computedUse();
console.log(`已用${useCache}KB`);
})()
可以查看已用容量
# 剩余容量
我们已经计算出总容量和已使用容量,那么剩余容量 = 总容量 - 已使用容量
const computedsurplus = (total, use) => {
return total - use;
}
(async () => {
const total = await computedTotal();
let o = '0123456789';
for(let i = 0; i < 1000; i++) {
o += '0123456789';
}
localStorage.setItem('o', o);
const useCache = computedUse();
console.log(`剩余可用容量${computedsurplus(total, useCache)}KB`)
})()