ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

AI分析结果如何用localStorage持久化:前端存储方案与踩坑指南

AI分析结果如何用localStorage持久化:前端存储方案与踩坑指南 写在前面写本地存储这块内容之前我先说一下为什么想写这篇。做前端这些年遇到过不少类似需求——AI分析结果出来了页面一刷新数据全没了。不管是调用完大模型接口拿到的文本分析结果还是本地Web推理产生的评分数据只要没做持久化一刷新就得重新等几秒甚至几十秒。后来我习惯性地用HTML5 localStorage把这些AI分析结果落地整个过程从分析完就丢变成了分析一次、随时可查。这篇想把自己在AI分析数据后通过localStorage做持久化的完整思路、踩坑记录、封裝套路和边界处理整理出来希望对你做类似功能时有参考价值。1. AI分析结果存不住从一次性页面到有记忆的前端1.1 真实痛点分析完成不落地等于白做我最早接触AI分析类的前端页面时踩过一次很实际的坑。当时做了一个文本情感分析工具页面把用户输入的一段文本发给后端推理服务后端返回一个JSON结果包括情感倾向、置信度、关键词列表、完整分析报告。数据拿到了页面也渲染了一切看起来正常。但用户一旦按F5刷新页面回到初始状态所有分析结果全部消失。用户反馈说我费了半天劲输入一大段内容、等了十几秒分析完你们连个结果都留不住这个反馈让我意识到一个关键问题AI分析的价值不只是算出结果还包括让结果可以被复用、可以被追踪、可以被二次查看。分析过程本身是计算密集型任务往往耗时几秒到几十秒如果结果不落地每次查看都要重新分析既浪费算力也消耗用户耐心。从技术角度说这类场景需要的是一个本地持久化方案——把AI分析产出的结构化数据保存在用户浏览器里让下次打开页面时能直接读取。而这个需求localStorage是最直接、最轻量的选择。1.2 为什么优先考虑localStorage而不是其他存储方案在浏览器端做数据持久化可选项其实不少cookie、sessionStorage、localStorage、IndexedDB还有Web SQL已废弃。但针对AI分析结果持久化这个场景我做过一轮对比结论很明确大部分场景下localStorage是最优解。存储方案容量上限持久性是否易用适用场景cookie约4KB可设置过期时间操作繁琐每次请求自动携带会话标识、少量状态信息sessionStorage约5MB标签页关闭即清除简单临时状态不跨会话localStorage约5MB持久保存手动清除才会消失非常简单AI分析结果、用户偏好、缓存数据IndexedDB数百MB甚至更多持久保存异步API学习成本高大数据量、二进制数据、离线应用表格里的对比很直观。localStorage的定位正好命中AI分析结果这种数据的特征单条分析结果一般在几十KB以内一段文本的分析报告、评分明细、结构化标签撑死了也就几KB到几十KB总量在几十到几百条之间5MB的配额绰绰有余持久性完全达标刷新页面、关闭浏览器再打开数据都还在API是同步的读写不需要处理回调代码心理负担最小。有人可能说既然IndexedDB容量那么大为什么不一步到位我的经验是IndexedDB的异步API和事务模型在前期开发中会拖慢节奏而且对于存一个几十KB的分析结果JSON这种需求属于杀鸡用牛刀。我的习惯是先上localStorage跑通全部逻辑等功能稳定、数据量确实逼近配额上限时再考虑迁移IndexedDB。后面第6章我会单独讲什么情况下必须迁移以及迁移的过渡方案。2. localStorage的工作机制与典型脾气做持久化之前先把localStorage本身摸清楚。它看起来就是setItem/getItem两个方法但真正用起来有不少暗坑。我把关键机制总结成四个性格搞懂这些后面写代码才不容易翻车。2.1 四个关键机制同源、同步、字符串、持久第一同源策略。localStorage是以源为单位隔离的。所谓源指的是协议域名端口三者的组合。http://localhost:8080和http://localhost:9090是两个完全不同的存储空间https://example.com和http://example.com也互不相通。这意味着你在开发环境localhost存的数据部署到线上域名后是读不到的——别指望用localStorage跨域名共享数据这是设计上就不支持的事情。如果你的AI分析工具涉及多个子域名比如app.example.com和api.example.com就需要明确localStorage只能存在前端页面所在的源里后端源只能用cookie或token来做身份关联。第二同步操作阻塞主线程。这点很容易被忽略。localStorage的读写是同步的而且发生在主线程上。虽然单个setItem的性能通常只有几毫秒但如果你的代码里在一个高频循环中反复读写localStorage或者存储的数据量偏大就可能阻塞渲染导致页面卡顿。AI分析结果一般不会特别大但如果是把整份分析报告拆成几百个key分别存储性能就会开始出现问题。我的原则是一次分析结果尽量打包成一个key整体写入而不是拆散成几十个小key。第三只能存字符串。localStorage真人如其名local——本地storage——存储存的都是字符串。想存对象、数组、数字、布尔值统统要先序列化成字符串。反过来读出来的是字符串要再解析成原本的数据类型。这个机制本身不复杂但坑在于很多人忘了JSON.parse可能抛异常。存进去的时候好好的取出来发现字符串格式不对一解析就报错页面直接崩。第4章我会给出一个带try-catch的封装就是专门解决这个问题的。第四数据永久保留除非主动清理。localStorage没有过期时间的概念。你存进去的数据用户不手动清除浏览器数据、网站代码不主动调用removeItem或clear它就会一直保留。对AI分析结果这种长期有价值的数据来说这是优点但对一些有敏感性的中间数据来说这也是个需要注意的性格——万一存了不该存的信息比如用户完整输入文本清理起来就麻烦。所以我在设计存储结构时特意加了元信息字段例如createdAt、expiresAt方便后续按时间批量清理。2.2 配额与隐私两个容易忽视的隐藏条件localStorage的配额在大多数浏览器实现中约为5MB按UTF-16编码的字符数计算中文、emoji会占更多空间。5MB听起来不小但要注意整个源共享这个配额不是每个key单独5MB。如果你的AI分析工具还缓存了图片、大段JSON、离线模型配置等这些都要算在同一个池子里。隐私模式无痕模式下localStorage的行为在各大浏览器中并不统一。有些浏览器在无痕模式下仍然提供localStorage但数据在会话结束后清除有些浏览器干脆在调用setItem时直接抛QuotaExceededError。我在实际测试中发现Safari的旧版本在无痕模式下写localStorage会直接报错而Chrome无痕模式则表现正常。所以写localStorage的代码必须包异常处理这不是理论上可能出错的问题而是实际一定会遇到的边界条件。后面第5章我会展开讲异常场景的完整链路。3. 设计存储结构让AI分析结果不乱、不脏、可追溯localStorage本身只管存字符串不管数据怎么组织。如果直接上来就localStorage.setItem(result, JSON.stringify(data))短期用着没问题但功能迭代一多就乱了。我推荐在做持久化之前先花十分钟设计一下存储结构。3.1 按分析批次建模一个分析任务对应一个存储键AI分析场景有一个特点一次分析会产出一条完整的结果记录包含输入内容摘要、输出结果、模型信息、耗时、时间戳等。如果用户反复分析不同内容就会产生多条记录。这些记录天然适合按批次存储。我的推荐结构是两条轨道一条总索引key存所有分析批次的元信息列表。比如ai_analysis_index存一个数组数组元素是对每个批次的描述批次ID、创建时间、结果类型、简要标签。一组详情key每个批次一条详情。键名格式为ai_analysis_detail_{batchId}value是该批次完整结果。这样的好处是读取总索引能快速展示历史分析记录列表只需解析一个小数组速度快点击某一条时再按batchId读取详情单条数据量虽然大但只在需要时读取不拖慢首屏。如果只用一个key保存所有内容数据量大了之后每次列表展示都要把全量数据解析一遍性能会明显变差。举个例子。用户分析了一篇小说片段的情感倾向产生了一个批次结构大致长这样{ batchId: 20250607153012_8f3a, createdAt: 1751881412345, sourceType: text, sourceDigest: 小说片段_前200字摘要..., result: { sentiment: mixed, score: 0.76, keywords: [孤独, 希望, 荒原], summary: 整体情绪先抑后扬孤独感为主基调... }, modelInfo: { name: sentiment-model-v2, version: 2.1.0, latencyMs: 3400 } }这条JSON被序列化后大小通常在几KB到几十KB之间对localStorage来说完全不是负担。每条记录里都带batchId和createdAt既方便列表排序也方便后面做定时清理。3.2 给存储数据加版本号应对AI模型升级后的结构变化AI领域变化很快。今天你存的分析结果结构是A明天换了新版模型返回的JSON结构变成B老数据就面临解析兼容问题。这个问题在localStorage持久化场景里特别现实因为localStorage没有数据库迁移工具旧数据会一直躺在浏览器里直到过期或被清除。我的做法是在索引里维护一个schemaVersion字段在每条详情里也带上这个字段。{ schemaVersion: 2, batches: [ { batchId: 20250607153012_8f3a, schemaVersion: 2 } ] }当代码读取一条详情时先看schemaVersion。如果版本号低于当前代码期望的版本就走一遍兼容逻辑——要么做字段映射要么提示用户该条记录已过期要么自动清理。这个设计起初看起来有点多余但经历过一次换模型导致线上老数据全部解析崩溃的事故之后我把它列为了必选项。具体说当时后端把keywords字段从数组改成了对象前端读取时还在用result.keywords.map(...)结果老数据一读就报错用户那边历史记录列表全部白屏。加版本号之后至少能做到老数据降级展示或者静默迁移。3.3 key命名与大小控制的建议key的命名需要克制不要乱起。我常用的命名规范是{业务前缀}_{数据类型}_{用途}。比较ai_result——太泛无法区分业务ai_analysis_index——清晰AI分析的总索引ai_analysis_detail_20250607——清晰某个批次的详情业务前缀的作用是防止同一个源下的其他业务模块发生key冲突。之前见过一个项目不同模块各自起名有的叫user有的叫user_data还有叫userInfo的三个key存储的内容根本不相干维护起来非常痛苦。关于大小控制我的建议是单条KV不超过100KB。localStorage本身能存更大的值但同步API的性能曲线在值超过一定大小后会明显变陡。AI分析结果的详情如果超过100KB通常意味着数据里有大量冗余内容比如完整原文、长日志。遇到这种情况我会把原文这种大字段拆出去或者压缩后存储只保留真正需要结构化展示的核心结果。实在拆不了的再考虑降级到IndexedDB这个判断标准我在第6章会详细说。4. 核心读写链路封装一个安全的持久化模块设计完存储结构下一步就是写代码。直接裸用localStorage.setItem肯定是不行的——前面说的隐私模式、配额超限、JSON解析异常、旧版本残留数据这些问题不处理线上早晚出事。我的做法是封装一个独立的小模块专门负责AI分析结果的持久化读写。4.1 带异常兜底的写入封装写入是整个链路的第一步AI分析完成后前端拿到结果调用写入方法把它们持久化。写入方法的代码如下我逐个解释关键点function saveAnalysisBatch(batchData) { // 参数校验 if (!batchData || typeof batchData ! object) { console.warn([Storage] 无效的分析批次数据); return false; } // 读取当前索引 let indexData { schemaVersion: 2, batches: [] }; try { const rawIndex localStorage.getItem(INDEX_KEY); if (rawIndex) { indexData JSON.parse(rawIndex); } } catch (e) { // 索引数据损坏时重建索引 console.warn([Storage] 索引解析失败重建索引); indexData { schemaVersion: 2, batches: [] }; } // 更新索引 indexData.batches.push({ batchId: batchData.batchId, createdAt: batchData.createdAt, schemaVersion: 2 }); // 写入详情 const detailKey ${DETAIL_PREFIX}${batchData.batchId}; try { localStorage.setItem(detailKey, JSON.stringify(batchData)); localStorage.setItem(INDEX_KEY, JSON.stringify(indexData)); return true; } catch (e) { if (e.name QuotaExceededError) { console.error([Storage] 存储空间已满写入失败); } else { console.error([Storage] 写入异常, e); } return false; } }这段代码解决了三个问题第一个问题是索引损坏后的自愈。localStorage.getItem(INDEX_KEY)拿到的字符串有可能不是合法JSON——可能是用户手动修改了、可能是写入中断导致半个字符串。如果直接JSON.parse一旦抛异常页面就崩。所以我把解析过程包在try-catch里解析失败时重建一个空索引。这个自愈逻辑非常实用因为localStorage是用户可干预的存储数据被篡改的情况比想象中常见。第二个问题是写入原子性的缺失。注意代码里先写了详情再写索引。如果两步之间抛异常就会出现详情存在但索引里没有记录的脏数据。我这里的策略是详情key用batchId作为后缀永远不会覆盖旧数据索引是列表结构就算丢失某条索引记录也只是列表看不到不会损坏已有数据。偶尔丢失一条索引记录可接受比整块数据损坏强得多。如果业务上要求绝对一致那就得引入先写索引再写详情的顺序并在读取时做兜底校验但那样代码复杂度会高不少我一般不做。第三个问题是空间不足时的显式报错。QuotaExceededError是localStorage最常见的异常之一。捕获到它之后不能只是静默吞掉至少要在控制台打出明确日志并且让上层调用方知道这次持久化失败了以便决定是提示用户清理缓存还是走降级方案。4.2 读取与解析的防护读取比写入更容易被坑到。因为localStorage里的数据可能是从未写入的null、写入过但被篡改的字符串、合法但版本过老的字符串。我的读取封装长这样function getAnalysisBatch(batchId) { if (!batchId) return null; const detailKey ${DETAIL_PREFIX}${batchId}; try { const raw localStorage.getItem(detailKey); if (!raw) return null; const parsed JSON.parse(raw); // 版本校验老版本数据结构需要迁移或降级 if (parsed.schemaVersion parsed.schemaVersion CURRENT_SCHEMA_VERSION) { console.warn([Storage] 检测到旧版本数据(${parsed.schemaVersion})当前版本(${CURRENT_SCHEMA_VERSION})); return migrateBatchData(parsed); } return parsed; } catch (e) { // 单条数据损坏不影响其他数据 console.error([Storage] 读取批次${batchId}失败, e); return null; } }读取逻辑里有两个容易被忽略的细节第一返回null而不是抛异常。当某条数据不存在、格式错误、版本不匹配时函数都返回null由上层UI决定怎么展示没有分析结果的状态。这个约定让调用方代码变得很干净——不需要到处写try-catch只要判断if (result)即可。第二迁移函数保持独立。migrateBatchData是一个可扩展的函数根据schemaVersion做不同版本的字段映射。最简单的情况是只改字段名复杂的情况可能需要调用AI接口重新分析。我通常只做前一种轻量迁移后一种成本太高不如直接提示用户重新分析。4.3 按批次清理根据ID删除localStorage数据清理这个需求很多业务里都会出现——用户主动删掉某条历史分析记录、系统清理过期缓存、存储空间超限后的自动清理。localStorage的removeItem很好用但直接裸用有一个坑删了详情忘了删索引或者删了索引忘了删详情都会留下脏数据。我习惯把按ID删除封装成一步到位的操作function deleteAnalysisBatch(batchId) { if (!batchId) return; // 1. 删除详情 const detailKey ${DETAIL_PREFIX}${batchId}; localStorage.removeItem(detailKey); // 2. 从索引中移除对应条目 try { const rawIndex localStorage.getItem(INDEX_KEY); if (!rawIndex) return; const indexData JSON.parse(rawIndex); const beforeCount indexData.batches.length; indexData.batches indexData.batches.filter(b b.batchId ! batchId); if (indexData.batches.length ! beforeCount) { localStorage.setItem(INDEX_KEY, JSON.stringify(indexData)); } } catch (e) { console.error([Storage] 索引清理失败, e); } }删除顺序上我选择先删详情再更新索引。原因和前边的写入顺序逻辑一致如果索引更新失败最多是索引里多一条指向不存在详情的记录读取时会拿到null不会报错反过来如果先删索引再删详情一旦详情删除失败就会出现看不见但占空间的孤儿数据。孤儿的危害在于用户永远不会主动去清理它长期积攒会白白浪费配额。如果需要清空所有AI分析数据直接localStorage.removeItem(INDEX_KEY)再遍历所有以ai_analysis_detail_开头的key逐个删除。遍历全部key的方式很简单function clearAllBatches() { const keysToRemove []; for (let i 0; i localStorage.length; i) { const key localStorage.key(i); if (key (key INDEX_KEY || key.startsWith(DETAIL_PREFIX))) { keysToRemove.push(key); } } keysToRemove.forEach(k localStorage.removeItem(k)); }5. 边界问题与踩坑清单localStorage持久化不会一帆风顺写完基本读写封装功能能跑了但这只是开始。真实线上环境里localStorage持久化会遇到各种稀奇古怪的问题。下面这些坑都是我实际踩过的按重要程度列出来。5.1 隐私模式与浏览器策略差异写入也会假装成功Safari旧版本在隐私模式下调用localStorage.setItem会直接抛异常——这个场景我在第2章提过。但更隐蔽的问题在Chrome无痕模式下写入localStorage不会抛异常数据看起来写成功了读出来也是对的但一旦关闭无痕窗口数据立刻消失。这种写着写着就没了的行为用户感知不到但程序必须感知。我的判断方案是写入后立刻读出来做一次校验。// 写入后校验确认数据真的落盘 function safeSetItem(key, value) { localStorage.setItem(key, value); const readback localStorage.getItem(key); if (readback ! value) { console.warn([Storage] 写入验证失败key${key}); throw new Error(storage-write-verify-failed); } }这种写入后回读校验虽然多了一次读取开销但对AI分析结果这种重要数据来说完全值得。它能有效识别出存储被禁用或者存储被模拟的场景。另外如果你的页面需要同时兼容PC浏览器和WebView比如内嵌在App里一定要在真机上测一遍WebView的localStorage行为。不少Android WebView默认设置下localStorage是可用的但iOS WKWebView在特定配置下可能会关闭localStorage支持或者每次进程回收后清空数据。这种环境差异只有实机测试才能发现。5.2 解析失败与半个JSON写入中断引发的脏数据localStorage的写入是同步的理论上是要么完全写入要么不写。但现实中有一种情况会导致半个JSON浏览器崩溃、页面被强制关闭、操作系统休眠导致写盘中断。虽然概率低但一旦发生数据就废了。这个情况只能靠读取端的try-catch兜住返回null让UI显示友好的空状态。值得一提的是千万不能在读取失败时采用尝试修复JSON的方式。有人会在JSON.parse失败后用字符串拼接、截取的方式尝试把残缺JSON补全我强烈不建议这么做。LocalStorage里的脏数据最好的处理方式就是删除或覆盖而不是修复。JSON结构一旦破损任何修复都是在制造新的不确定性。5.3 多标签页并发同时写索引会互相覆盖如果你的AI分析工具允许用户开多个标签页同时使用localStorage的同源共享特性就会带来并发问题。两个标签页同时读取索引、各自往自己的副本里追加记录、再写回——后写入的那个会把先写入的那个覆盖掉造成记录丢失。这个问题的经典解法是监听storage事件。当某个标签页修改了localStorage时其他同源标签页会收到storage事件通知。处理策略是写入索引前先临时监听storage事件一旦发现其他标签页也在这个间隙修改了索引就重新读取索引再合并。但这样代码会比较复杂。对于大部分AI分析工具的使用场景多个标签页同时做分析的概率其实不大我的建议是先用写前重读来降低风险——在setItem之前重新getItem一次把最新索引读出来再追加。这样虽然不能完全解决并发但在低并发场景下已经能避免绝大部分覆盖问题。代码如下function appendBatchToIndex(batchMeta) { // 写前重读降低覆盖概率 let indexData { schemaVersion: 2, batches: [] }; const raw localStorage.getItem(INDEX_KEY); if (raw) { try { indexData JSON.parse(raw); } catch (e) { indexData { schemaVersion: 2, batches: [] }; } } indexData.batches.push(batchMeta); localStorage.setItem(INDEX_KEY, JSON.stringify(indexData)); }5.4 存储配额耗尽从写不进去到存量数据治理配额耗尽比想象中更容易遇到。QuotaExceededError出现时最直接的响应是提示用户清理缓存但更好的做法是在写入前主动检查剩余空间。localStorage本身没有剩余空间查询接口但你可以用一个变通方法function getLocalStorageUsage() { let total 0; for (let i 0; i localStorage.length; i) { const key localStorage.key(i); const value localStorage.getItem(key); // 粗略按字符数估算字节数1字符 ≈ 2字节 total (key.length (value ? value.length : 0)) * 2; } return total; }这个函数返回当前源下所有localStorage数据的估算字节数。在写入AI分析结果前调用一次超过设定阈值比如4MB就触发过期数据清理流程把超过保留期限的历史分析记录按批次删除。关于保留期限我给AI分析结果的默认策略是保留最近30天的记录超过30天的按批次自动清理。因为分析结果的有效性通常会随时间衰减尤其是基于模型的分析模型升级后旧结果参考价值会打折扣。清理逻辑可以放在页面初始化时异步执行不阻塞主流程。6. 更进一步把localStorage和AI分析流程整合得更优雅基础读写和异常处理做完这个功能已经可用了。但如果想让体验更好、更扛得住真实业务还有几个优化方向值得做。下面这几点是我在项目中验证过有效的策略。6.1 分析结果去重与缓存命中避免重复分析AI分析接口通常不是免费的无论是调用外部API还是本地推理都有成本。localStorage天然适合做分析结果的缓存层。用户输入一段文本如果之前已经分析过同样的内容可以直接从localStorage读出结果不再调用分析接口。实现这个逻辑的关键是输入内容哈希。在用户触发分析前前端对输入文本做一次hash计算可以用简单字符串摘要函数或者引入crypto.subtle做SHA-256把这个hash作为缓存的key之一。下次用户再输入相同内容时直接从localStorage查找。当然这个策略有一个前提分析模型和参数没有变化。如果模型升级了之前缓存的结果就没意义了。所以缓存key最好带上模型版本号function buildCacheKey(modelVersion, inputText) { const hash simpleHash(inputText); return ai_analysis_cache_${modelVersion}_${hash}; }我在实际项目里测试过同一段文本的重复分析率大约在20%-30%。启用localStorage缓存后这部分请求直接过滤掉页面响应速度肉眼可见地提升后端调用成本也降了。6.2 结果分层热数据与冷数据分开存随着使用时间变长localStorage里积攒的分析记录会越来越多。如果全部放在localStorage里最终会碰到配额上限。我采用的分层策略是热数据保留最近7天存在localStorage读取速度快用于最近分析列表的即时展示。冷数据7天以前存在IndexedDB读取速度略慢用于历史归档查询。可丢弃的中间数据比如分析时的临时状态、未完成的输入草稿超过24小时直接清掉。这个策略的好处是localStorage里永远只保留一小部分热数据配额压力小历史记录也没有丢失只是迁移到了更大的存储池。每次写入新记录时顺手检查一下已有记录的时间戳把超过7天的记录转移到IndexedDB。这个迁移过程可以在后台异步做用户无感知。6.3 什么时候必须从localStorage迁移到IndexedDB如果你的一切都做得比较到位localStorage的5MB配额用了好几个月还没满那完全可以继续用。但出现以下信号时就该考虑迁移了单日新增分析记录超过100条预计每月新增数据量突破2MB单条分析结果因为包含完整原文或长文本摘要体积经常超过100KB页面有离线使用需求需要缓存大量历史分析数据分析结果中包含图片、音频等二进制数据迁移不要做成一次性把所有数据搬过去的大动作而是双写过渡新数据同时写localStorage和IndexedDB旧数据在读取时按需迁移。这个思路和缓存迁移的做法类似风险最小用户无感知。等到IndexedDB那边数据完全覆盖了localStorage再把localStorage的老数据清理掉。我见过一个团队试图在某个版本里一次性把几十MB的localStorage数据搬到IndexedDB结果老浏览器不支持IndexedDB、迁移脚本异常、用户历史记录全丢教训很大。渐进式迁移虽然慢但稳。7. 写在最后的经验总结这套AI分析结果localStorage持久化的方案在我手里迭代了好几轮从最初裸用setItem到后来封装异常处理、版本控制、分层存储踩的坑不少但收获也一样多。核心心得有三条。第一持久化方案的选型不要只看容量还要看数据的使用方式。AI分析结果这类数据的特点是单条小、总量可控、读取频率不高、需要跨会话保留localStorage的同步API和简单操作正好匹配。容量不够时再考虑IndexedDB但也不要轻易上复杂度是实打实的。第二localStorage代码必须把异常处理当标配。隐私模式、配额超限、JSON损坏、版本不匹配这些不是可能发生的事件而是一定会发生的事件。到处try-catch虽然不好看但比线上白屏强一百倍。第三存储结构设计要像设计数据库表一样谨慎。版本号、批次ID、索引与详情分离、命名规范——这些在项目初期看似多余的设计在后来的每一次迭代里都在帮我省时间。特别是版本号AI模型升级是家常便饭没有版本号老数据早晚变成定时炸弹。如果你也在做一个AI分析相关的工具希望这篇文章能帮你少踩几个坑。篇幅所限像IndexedDB迁移适配老浏览器、storage事件做多标签页同步这些细节没法完全展开后面有机会可以再单独分享。
返回列表