本地存储这玩意儿,平时用着挺顺手,一上线就给你整点幺蛾子。我做过好几个中大型前端项目,从后台管理系统到高并发H5页面,都在本地存储上踩过坑,而且踩得相当瓷实。今天就把这些年攒下的经验和教训一次性捋清楚,围绕“本地存储爆雷怎么办”这件事,把我自己用的5套解决方案完整拆开讲。
1. 爆雷不是偶然,先搞懂本地存储的“隐性缺点”
很多前端新手对localStorage的理解就是“能存点东西”,用完就忘。但真正到了线上环境,尤其是用户量大、机型杂、网络不稳定的场景,本地存储的短板就会被无限放大,最后变成线上事故。
1.1 容量瓶颈和同步阻塞是两道硬门槛
localStorage的容量限制一般是5MB左右,这个数字在不同浏览器上还有浮动。看着挺大,但你要是往里面塞用户行为日志、缓存接口数据、图片Base64,很快就满了。最麻烦的是,localStorage读写是同步操作,而且阻塞主线程。假如你在页面初始化时循环写入几百条数据,用户会明显感觉到卡顿;如果正好赶上页面在跑动画或者用户正在输入,那种卡顿就会直接劝退用户。
我遇到过最夸张的一次,一个运营后台页面用localStorage存了大概4MB的表单草稿,结果每次打开页面都要白屏将近两秒,光看控制台就知道是读取localStorage占了大头。
1.2 没有过期时间,旧数据会变成“僵尸数据”
localStorage设计的初衷是持久化存储,但恰恰是“持久”这两个字,在真实业务里会引发严重问题。接口返回的数据结构是会变的,你第一版存进去的是 {name: "张三", age: 18},第二版接口改成了 {userName: "张三", userAge: 18},这时候如果你的前端代码没有做兼容处理,读取localStorage就会拿到一堆undefined,页面直接渲染异常。
还有一个更隐蔽的问题:用户长时间不清理浏览器缓存,localStorage里存的过期版本数据就会一直驻留。时间一长,这些僵尸数据不仅占用空间,还会在你做功能迭代时莫名其妙地触发各种历史遗留bug。
1.3 用户行为不受控,存储随时可能“被消失”
你以为存了就安全?太天真了。用户可以用隐私模式、可以主动清理网站数据、可以用各种“清理垃圾”软件无差别清除浏览器存储。尤其是在安卓WebView内嵌H5的场景,系统省电模式、内存清理助手都会把WebView的数据目录清掉。
也就是说,本地存储天然就是个“不可靠”的存储层。如果你把唯一的关键数据(比如用户未同步的操作记录)只放在本地存储里,那数据丢失就是迟早的事。明白了这些底层限制之后,再来看具体的解决方案,思路就清晰多了。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 第一招:按场景分层选型,别把localStorage当万能仓库
解决问题的第一步,不是上来就写代码,而是先把“数据该放哪”这件事想清楚。本地存储不只有localStorage一种选型,正确做法是根据数据特征做分层设计。
2.1 localStorage、sessionStorage、IndexedDB怎么选
这三种方案适用于完全不同的业务场景,我直接拿自己常用的选型表来说明。
| 存储方案 | 容量上限 | 生命周期 | 同步/异步 | 适用场景 |
|---|---|---|---|---|
| localStorage | 约5MB | 持久,需手动清理或代码清理 | 同步 | 偏好设置、登录令牌、简单缓存 |
| sessionStorage | 约5MB | 标签页关闭即销毁 | 同步 | 表单草稿、页面临时状态、跨页面传参 |
| IndexedDB | 数百MB甚至更多 | 持久,需手动清理 | 异步 | 大数据缓存、文件存储、离线数据 |
这套选型逻辑的核心是:能用sessionStorage就不用localStorage,能用IndexedDB就不硬塞localStorage。
比如表单草稿这种数据,它本来就只服务“当前这次填写过程”,用户关掉页面后还留着反而容易造成下次打开时数据错乱。所以用sessionStorage更合理,生命周期天然匹配。
再比如用户浏览历史记录、列表页接口缓存这类体积大、需要长期保存的数据,用localStorage会频繁踩到容量上限。这时候换成IndexedDB,几百MB的容量足够你从容存储,而且异步读写不阻塞UI渲染。
2.2 存储前先做容量预估和写入保护
我见过很多项目,代码里连try-catch都不做就直接 localStorage.setItem,这其实是埋雷行为。localStorage读写都是有可能会抛异常的,最常见的就是 QuotaExceededError(配额超限)。而且部分浏览器隐私模式下,任何localStorage操作都会抛异常。
所以我在项目里会封装一个本地存储工具层,核心是两个逻辑:一是写入前预检剩余容量,二是所有读写都做异常捕获。
javascript复制const StorageUtil = {
// 预检剩余空间
checkQuota(key, value) {
const itemSize = encodeURIComponent(JSON.stringify(value)).length;
const currentSize = encodeURIComponent(JSON.stringify(localStorage)).length;
const quotaSize = 5 * 1024 * 1024; // 按5MB估算
return currentSize + itemSize < quotaSize;
},
// 安全写入
setItem(key, value) {
try {
const data = JSON.stringify(value);
if (!data) return false;
if (!this.checkQuota(key, value)) {
console.warn('[StorageUtil] 存储空间不足,写入前清理旧数据');
this.cleanUp();
}
localStorage.setItem(key, data);
return true;
} catch (e) {
console.warn('[StorageUtil] 写入失败:', e);
return false;
}
},
// 清理过期数据
cleanUp() {
const prefix = 'cache_';
const now = Date.now();
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
if (key && key.startsWith(prefix)) {
try {
const meta = JSON.parse(localStorage.getItem(key));
if (meta.expire && meta.expire < now) {
localStorage.removeItem(key);
}
} catch (e) {
localStorage.removeItem(key);
}
}
}
}
};
这段代码看起来简单,但解决了很多真实的线上问题。checkQuota 每次写入前把当前容量和数据量估算一下,超过阈值就触发 cleanUp 清理过期缓存。这样做的好处是:你永远不会在写入的时候突然炸掉,一切都在可控范围内。
2.3 什么时候才需要用IndexedDB?拿一个真实例子说明
之前做资讯类H5的时候,有一个首页信息流需求,用户每次打开首页都要拉取上一次浏览的20条资讯做秒开展示。20条资讯的结构化数据加上图片配置信息,至少得2MB起步,用localStorage虽然能塞下,但读取时会阻塞渲染,用户感知明显变卡。
后来我改成了IndexedDB存储,用Dexie这个封装库操作,读写都是异步的,既不阻塞主线程,容量也足够。整个改造大概花了半天时间,用户体验提升是肉眼可见的。
简单给你一段IndexedDB的关键操作参考:
javascript复制import Dexie from 'dexie';
const db = new Dexie('NewsCache');
db.version(1).stores({
news: 'id, timestamp'
});
// 保存资讯列表
export async function saveNews(list) {
await db.news.bulkPut(list.map(item => ({
id: item.newsId,
timestamp: Date.now(),
data: item
})));
}
// 读取最近浏览的资讯
export async function getRecentNews(limit = 20) {
const cached = await db.news
.orderBy('timestamp')
.reverse()
.limit(limit)
.toArray();
return cached.map(item => item.data);
}
这种方案的收益很明显:异步读取不需要等数据全量解析完就能先渲染页面骨架,资讯内容到了之后再做填充,实际体验就是“页面秒开”,而且数据量再大也不影响主流程性能。
3. 第二招:给缓存上版本号,用代码管住旧数据
缓存最让人头疼的问题就是“数据是旧的”,而且你根本不知道它是哪个年代留下的。我给所有前端项目定了一个硬性规范:本地存储的key必须带版本标识,数据写入时必须记录过期时间。
3.1 Key命名规范和版本号设计
先看一个反例:项目中懒省事,直接用 userInfo、cartList 这种裸key,后来接口改版了,数据结构变了,线上老用户的数据直接读取失败,只能临时发版把缓存清掉。这就是典型的没有版本管理意识。
我现在的做法是所有key统一走常量管理,并且带上版本号。
javascript复制const STORAGE_KEYS = {
USER_INFO: 'app_user_info_v2',
CART_LIST: 'app_cart_list_v2',
NEW_LIST: 'app_news_cache_v2'
};
这个 _v2 后缀很关键,当后端接口结构升级时,前端代码相应改成 _v3,实现“物理隔离”。旧版本的缓存数据因为key变了,自然就不会被读取到,但也不会立刻删除——可以做延迟清理,避免用户升级后第一次打开页面时多一次接口请求。
不过光改key还不够,历史残留数据迟早要清。可以在应用启动时做一次缓存清理,遍历所有key,凡是符合我们这个应用的前缀(比如 app_)但版本号不是最新的,直接删掉。
javascript复制export function clearOldVersionCache() {
const prefix = 'app_';
const currentVersionKeys = Object.values(STORAGE_KEYS);
for (let i = localStorage.length - 1; i >= 0; i--) {
const key = localStorage.key(i);
if (key && key.startsWith(prefix) && !currentVersionKeys.includes(key)) {
localStorage.removeItem(key);
console.warn(`[Storage] 已清理旧版本缓存: ${key}`);
}
}
}
这段代码每次版本迭代时都会自动清理掉上一个版本的残留数据,长期运行下来localStorage不会越积越满,也不会出现旧数据影响新逻辑的坑。
3.2 给数据加上过期时间戳
版本号解决的是“本轮迭代内”的数据混乱,而“过期时间”解决的是“同一版本下”的数据生命周期问题。localStorage本身没有过期机制,所以我们自己给数据包一层元信息。
javascript复制export function setCache(key, value, expireSeconds = 3600) {
const payload = {
data: value,
expire: Date.now() + expireSeconds * 1000
};
localStorage.setItem(key, JSON.stringify(payload));
}
export function getCache(key) {
try {
const raw = localStorage.getItem(key);
if (!raw) return null;
const { data, expire } = JSON.parse(raw);
if (expire && Date.now() > expire) {
localStorage.removeItem(key); // 惰性删除
return null;
}
return data;
} catch (e) {
return null;
}
}
读取缓存时会检查过期时间,发现过期就顺手删掉。这种“惰性删除”策略的好处是代码简单,不需要额外起定时器去扫描所有key,只在业务读取时顺带清理。我用这种模式缓存接口数据,一般设置5到10分钟的过期时间,既保证了页面的快速展示,又不至于让数据陈旧太长时间。
3.3 缓存与后端接口版本的联动思考
本地缓存版本号的本质,其实是和后端接口协商好“缓存契约”。如果你上线的接口改了字段名、改了数据类型,前端必须同步提升缓存版本号。好的做法是在接口文档里就明确标注“此接口数据结构变更,涉及本地缓存需升级版本”。
我踩过一次很惨的坑:后端上线新接口时,没有提前通知字段变更,结果线上所有用户的本地缓存都是旧结构,导致首页白屏了一整天,最后紧急回滚。后来我们约定,涉及接口结构变更必须提前在群里同步前端,同时前端代码里用类型校验兜底——读取缓存时如果校验失败,直接丢弃缓存重新请求。
4. 第三招:存储变更联动,多标签页共享的“隐形通信”
本地存储还有一个常被忽略的痛点:多标签页之间的状态同步。用户在A标签页改了购物车数据,B标签页还停留在旧数据上,一刷新就丢。这种问题虽然不致命,但很影响体验。
4.1 storage事件怎么处理多标签页同步
localStorage原生提供了storage事件,当一个标签页修改localStorage时,其他标签页会触发这个事件。但这里有几个坑:
- 当前页面修改localStorage不会触发当前页面的storage事件
- 不同浏览器对这个事件的触发时机和参数支持略有差异
- 频繁写入时,storage事件会高频触发,需要做节流
我封装了一个简单的跨标签页广播方法:
javascript复制export function subscribeStorageChange(callback) {
window.addEventListener('storage', (e) => {
if (!e.key || !e.newValue) return;
const data = tryParse(e.newValue);
callback(e.key, data);
});
}
export function notifyStorageChange(key, value) {
localStorage.setItem(key, JSON.stringify(value));
// 当前页面的storage事件不会触发,需要手动调用本页面回调
handleLocalChange(key, value);
}
这个方案的思路是:跨标签页靠storage事件同步,同一标签页内用自定义事件分发。这样不管是哪个标签页修改了公共缓存数据,所有页面都能收到通知并更新UI。
4.2 更新缓存时用防抖合并写入
只要涉及localStorage写入,就得考虑写入频率的问题。比如用户在搜索框连续输入关键词,你每次输入都要把关键词存下来,如果直接无脑写入,写几十次localStorage,页面性能立马就崩了。
我会用防抖把连续写入合并成最后一次,或者用节流限制最小写入间隔。
javascript复制let writeTimer = null;
export function debounceWriteCache(key, value, delay = 500) {
if (writeTimer) clearTimeout(writeTimer);
writeTimer = setTimeout(() => {
localStorage.setItem(key, JSON.stringify(value));
writeTimer = null;
}, delay);
}
同样的逻辑也适用于页面滚动位置保存、用户行为埋点批量上报等场景。核心思路是:能用内存做实时状态,就尽量少碰localStorage;只有到了“需要持久化”的那个时刻,再做一次落盘。
4.3 存储写入失败时怎么降级不崩
隐私模式下、Safari旧版本、部分低版本安卓WebView,都可能让localStorage整体失效。这时候如果你的业务强依赖localStorage,页面就会直接报错。我的做法是在初始化时就做探针检测,检测失败则切换到内存存储方案。
javascript复制const memoryStorage = new Map();
export function getStorageEngine() {
try {
const testKey = '__storage_test__';
localStorage.setItem(testKey, '1');
localStorage.removeItem(testKey);
return localStorage;
} catch (e) {
console.warn('[Storage] 当前环境不支持localStorage,降级为内存存储');
return memoryStorage;
}
}
降级方案虽然牺牲了持久化能力,但至少保证页面的核心功能能正常跑。等用户刷新页面后数据就丢了,总比整个页面白屏强得多。
5. 第四招:存储结构设计,用“事件驱动”替代“硬读硬写”
本地存储如果只是存一些简单状态,那直接读写就够了。但一旦涉及多个业务模块共享同一份缓存数据,或者一个模块改了缓存、另一个模块需要感知变化时,就要做结构设计,而不是东写一段西写一段。
5.1 把缓存写入收敛到一个模块里
不夸张地说,很多项目就是“存储地狱”——每个页面各写各的,同一个key在不同地方被不同团队按不同格式写入。最后数据格式对不上,排查起来极其痛苦。
我的建议是:项目里只保留一个缓存管理模块,所有业务数据的读取、写入、删除都统一走这个模块的接口。
javascript复制class CacheManager {
constructor() {
this.listeners = new Map();
}
get(key) { /* 统一读取逻辑 */ }
set(key, value) {
// 统一写入逻辑
// 统一版本管理和过期控制
// 通知所有监听者
this.emit(key, value);
}
subscribe(key, callback) {
if (!this.listeners.has(key)) {
this.listeners.set(key, new Set());
}
this.listeners.get(key).add(callback);
}
emit(key, value) {
if (this.listeners.has(key)) {
this.listeners.get(key).forEach(cb => cb(value));
}
}
}
这样一来,业务代码只跟 cacheManager.get() 和 cacheManager.set() 打交道,底层是localStorage还是IndexedDB,对业务层完全透明。将来要迁移存储方案,只改这个模块就够了。
5.2 用自定义事件解耦状态联动
跨模块联动用自定义事件是个很成熟的方案。比如用户登录状态变化后,购物车、个人中心、下拉菜单等多个组件都要刷新数据。与其在各个组件里轮询,不如在登录状态变化时发一个自定义事件。
javascript复制// 登录成功之后
cacheManager.set('app_user_info_v2', userInfo);
window.dispatchEvent(new CustomEvent('user-login-change', {
detail: { userId: userInfo.id }
}));
// 需要感知登录状态变化的组件里
window.addEventListener('user-login-change', (e) => {
const { userId } = e.detail;
fetchUserCarts(userId);
fetchUserOrders(userId);
});
这个方案的好处是逻辑清晰、维护方便。组件之间不直接依赖,而是通过事件总线通信,新增一个需要响应登录状态的组件时,只需要多注册一个监听器就行了,不影响原有逻辑。
5.3 分区存储,避免大key拖垮整体性能
localStorage是按整体空间算的,把所有东西塞进一个大key里,一旦数据量大了,每次读取都要解析整个大JSON,性能会很差。我的习惯是分域存储——按业务模块把缓存拆成多个小块。
javascript复制// 不推荐
localStorage.setItem('all_data', JSON.stringify({ user: {}, news: [], cart: [], history: [] }));
// 推荐
localStorage.setItem('app_user_info_v2', JSON.stringify({ name: '张三' }));
localStorage.setItem('app_news_cache_v2', JSON.stringify([{ id: 1 }, { id: 2 }]));
分域存储的好处,一是读数据时不需要把一大堆不相关的数据一起解析出来,减少性能损耗;二是清理数据时粒度更细,只清某个模块的缓存不影响其他模块;三是对版本升级友好,某个模块升级了数据结构,只需要改那一个key的版本号。
6. 第五招:页面秒开从缓存开始联动HTTP层
本地存储和HTTP缓存是两个层面的缓存,但它们其实是同一个目标的两种手段——让用户少等、秒开。我在项目里常常把它们配合起来用,效果远好于单独用任何一种。
6.1 本地缓存做渲染骨架,HTTP缓存做资源加速
秒开的本质是“用户看到的渲染内容尽可能少等待”。从这个目标出发,首屏数据如果能有本地缓存,就先渲染出来,同时后台重新请求接口拿最新数据,发现差异再更新。
具体流程是:
- 页面启动,先读localStorage里的首屏缓存数据,直接渲染页面
- 同时异步请求接口
- 接口返回新数据后与缓存数据对比
- 有变化则更新本地缓存并刷新页面渲染
这个方案看起来简单,但有几个实现细节要注意。一是本地缓存数据的结构要足够精简,只保留渲染必需的最小字段;二是二次更新时不要整页刷新,而是做diff后只更新变化区域;三是接口请求失败时,要能保证用户看到的是本地缓存版本,而不是白屏。
6.2 Service Worker离线缓存补位
如果你的项目是PWA或者对离线访问有要求,Service Worker + Cache Storage是强大的补位方案。我第一次在移动端项目里接入Service Worker时,一个最直观的改善是:弱网环境下,用户打开页面不再白屏了,而是立刻显示缓存过的页面骨架。
Service Worker的思路是在首次访问后,把静态资源(JS、CSS、图片)和部分页面数据存到Cache Storage里。后续访问时,即使网络不畅,Service Worker也能直接从缓存里返回这些资源。
javascript复制// Service Worker 关键部分
self.addEventListener('fetch', (event) => {
const url = new URL(event.request.url);
// 静态资源:缓存优先,网络回退
if (event.request.url.includes('/static/')) {
event.respondWith(
caches.match(event.request).then((cached) => {
return cached || fetch(event.request).then((response) => {
const clone = response.clone();
caches.open('app_static_v1').then((cache) => {
cache.put(event.request, clone);
});
return response;
});
})
);
return;
}
// 页面数据接口:网络优先,缓存兜底
if (event.request.url.includes('/api/')) {
event.respondWith(
fetch(event.request).then((response) => {
const clone = response.clone();
caches.open('app_api_v1').then((cache) => {
cache.put(event.request, clone);
});
return response;
}).catch(() => {
return caches.match(event.request);
})
);
return;
}
});
这里有个策略取舍:静态资源用“缓存优先”,因为这类文件带指纹,更新了文件名就会自然掉缓存;接口数据用“网络优先”,因为数据变化频繁,必须保证用户看的是最新的。
6.3 缓存命中率分析,别让缓存形同虚设
搞了这么多缓存方案,怎么知道它真的生效了?我的习惯是在开发调试阶段就打开DevTools的Network面板,重点看每个请求的Size列。如果显示 (memory cache) 或 (disk cache),说明命中HTTP缓存;如果显示实际传输体积,说明没命中。
还有一个更实用的检查点:在Application面板的Local Storage、IndexedDB、Cache Storage里,能直接看到当前站点的本地缓存总量。我会定期看自己项目的生产环境缓存增长速度,如果增长异常快,就要检查是不是哪里没有设置过期时间。
另外关于接口缓存命中率,我建议在接口响应头里加上缓存标识,在前端代码里做个简单的埋点统计:
javascript复制let cacheHitCount = 0;
let cacheMissCount = 0;
export function recordCacheStatus(source) {
if (source === 'local-cache' || source === 'memory-cache' || source === 'disk-cache') {
cacheHitCount++;
} else {
cacheMissCount++;
}
if (cacheHitCount + cacheMissCount % 50 === 0) {
console.log(`[缓存命中率] ${(cacheHitCount / (cacheHitCount + cacheMissCount) * 100).toFixed(2)}%`);
}
}
用这个数据可以动态调优缓存策略,比如发现某个接口的本地缓存命中率极低,说明过期时间设置得过短,或者这个接口的数据本身就不适合缓存。
7. 常见问题与排查技巧实录
最后这部分,直接上实战高频问题。这些坑都是我线上真实遇到过的,整理出来给你当排查手册用。
7.1 常见问题速查表
| 问题现象 | 根本原因 | 解决方案 |
|---|---|---|
| 页面打开后白屏,控制台报QuotaExceededError | localStorage空间已满,写入失败 | 写入前预检容量,设置过期清理机制 |
| 同一页面开了多个标签页,数据不一致 | storage事件只通知其他标签页,当前页不受影响 | 用自定义事件手动同步当前页数据 |
| 本地缓存的数据是旧的,用户看不到最新内容 | 没有设置过期时间或过期时间过长 | 给缓存数据加expire时间戳,到期自动清理 |
| 隐私模式下页面报错 | 浏览器禁用了localStorage | 初始化时做探针检测,降级为内存存储 |
| 清除浏览器缓存后,用户登录状态丢失 | 登录token误存在localStorage且被清理 | token用httpOnly Cookie存储,或同时做多级持久化 |
| WebView内页面秒开效果不一致 | 不同安卓机WebView清理缓存策略不同 | Service Worker离线缓存兜底 |
7.2 三个排查本地存储问题的小技巧
排查本地存储问题,大部分情况靠DevTools就能定位。第一个技巧是,在Application面板里直接查看Local Storage,能看到所有key和value,但是当值比较长时很难直观判断格式是否正确。我的习惯是点一下key,在下方Preview里查看JSON结构化视图,能快速定位字段缺失、类型不对等问题。
第二个技巧是,在Network面板里关注接口请求的时间线和Size字段。如果一个接口显示 (memory cache),说明它在同一页面生命周期内被多次请求,且有HTTP缓存生效;如果显示 (disk cache),说明资源来自磁盘缓存;如果显示实际传输体积,说明这次是真正的网络请求。这三个状态能帮你判断缓存链路是否正常。
第三个技巧特别冷门但很实用:在Console里手动执行localStorage操作,快速模拟用户存储场景。
javascript复制// 模拟存储空间不足
let i = 0;
try {
while (true) {
localStorage.setItem('test_full_key_' + (i++), '1'.repeat(1024 * 100));
}
} catch (e) {
console.log('存储上限触发,总计写入:' + (i * 100) + 'KB', e.name);
}
这段代码能帮你实测当前浏览器的localStorage容量上限,以及触发异常时的完整调用栈。我在排查用户反馈“页面有时打不开”的问题时,就是用这种方法复现了配额超限导致的写入失败。
7.3 设计模式层面减少缓存出错概率
除了事后排查,更好的方法是事前减少出错的概率。我在项目中逐步推行的几个设计规范,供你参考。
一是所有缓存key都做集中管理,禁止业务代码直接写裸字符串key。统一放在一个常量文件里,加注释标明用途、版本、到期时间,这样任何人接手项目都能迅速了解现有的缓存结构。
二是所有缓存数据的读写都加统一的序列化和反序列化逻辑。不要业务代码里各处自己拼JSON,统一由工具函数处理,出错时只需要排查一处。
三是缓存读取处默认带数据校验。从存储里读出来的数据,先校验字段、类型、完整性,校验不通过就丢弃,走默认值或者重新请求。这个逻辑能把“脏数据”隔离在业务逻辑之外,防止它污染渲染层。
四是对缓存写入做统一日志。至少在开发环境,每次写入localStorage都打印一条日志,记录key、数据大小、剩余空间。这样在联调阶段就能尽早发现不合理的写入行为,而不是等上线后被用户投诉。
最后分享一点自己的体会
本地存储这块,我做项目时最大的感悟是:不要把它当成一个普通的API去用,而是要当成一个“随时可能失效”的独立系统来设计。所有的方案,本质上都是围绕可靠性、容量、性能这三个维度在做取舍。没有一套方案能适配所有场景,关键是结合自己的业务类型,提前做好选型和降级预案。
另外还想多说一句,前端本地存储有时候看起来很简单,但真正的影响范围远超想象——从首屏性能、用户数据安全、多端同步,到后续迭代成本。你前期花在设计上的每一分钟,都会在后期的稳定性上得到补偿。我见过太多项目前期图省事,后期为本地存储的事故填坑填到怀疑人生。这篇内容里提到的所有方法和代码,都是我项目里的沉淀,你可以直接用,也可以根据自己的业务做调整。如果实际应用时碰到其他坑,欢迎多交流。
