先產(chǎn)品仍需要備份出口)
把筆記放在瀏覽器里很適合一個不想強(qiáng)迫用戶注冊的小產(chǎn)品打開即寫離線可讀也少了一套賬號服務(wù)??墒恰氨镜貎?yōu)先”容易被誤說成“永不丟失”。瀏覽器緩存、用戶清理、隱私窗口、設(shè)備損壞都不會因?yàn)楫a(chǎn)品小而繞開它。保存狀態(tài)與備份狀態(tài)分開顯示MDN 的存儲與清理說明區(qū)分默認(rèn)的 best-effort 存儲與持久化申請。申請成功也不阻止用戶主動刪除數(shù)據(jù)。界面上可以顯示“已保存到此瀏覽器”不該直接寫“永久保存”。另一個明確動作應(yīng)該是“導(dǎo)出備份”。我會讓第一版產(chǎn)品保存一份帶版本號的 JSON并提供導(dǎo)入。版本號讓未來遷移有落腳點(diǎn)字段檢查則避免把損壞文件或其他產(chǎn)品的數(shù)據(jù)當(dāng)成自己的筆記。導(dǎo)入前先預(yù)覽數(shù)量不悄悄覆蓋現(xiàn)有記錄。同步、加密和備份是三件事不能靠一個云朵圖標(biāo)混在一起。賬號并不是默認(rèn)答案Chrome 在 10 月 5 日介紹的 Email Verification API 變更仍涉及 origin trial 和版本差異。對于一個小型離線工具這類登錄能力可以關(guān)注卻不適合當(dāng)作所有瀏覽器已經(jīng)具備的基礎(chǔ)設(shè)施。先把數(shù)據(jù)帶得走往往比先加登錄更能降低用戶的遷移成本。下面的代碼通過注入 storage 接口演示保存與載入。Node 中用內(nèi)存對象做替身瀏覽器使用時才替換為localStorage并在調(diào)用處捕獲配額、權(quán)限等異常。測試替身能驗(yàn)證序列化與校驗(yàn)流程不能證明瀏覽器不會清理數(shù)據(jù)。執(zhí)行node notes.js驗(yàn)證保存后重載、壞版本拒絕和重復(fù) ID 拒絕。本例沒有跨設(shè)備同步?jīng)]有測 IndexedDB也沒有將備份文件寫到云端。小產(chǎn)品不必一開始就解決所有災(zāi)難但必須給用戶一個清楚、可以親手拿走的出口??瑟?dú)立運(yùn)行的核心例子function decode(raw) { const xJSON.parse(raw); if(x.version!1||!Array.isArray(x.notes)||x.notes.length1000) throw Error(不支持的備份格式); const idsnew Set(); for(const n of x.notes) { if(typeof n.id!string||!n.id||ids.has(n.id)|| typeof n.text!string||n.text.length20000) throw Error(筆記字段無效); ids.add(n.id); } return x; } function repository(storage) { return { save(notes) {const rawJSON.stringify({version:1,notes});decode(raw);storage.setItem(notes-v1,raw);return raw;}, load() {const rawstorage.getItem(notes-v1);return raw?decode(raw).notes:[];} }; } const memorynew Map(); const storage{setItem:(k,v)memory.set(k,v),getItem:kmemory.get(k)??null}; const reporepository(storage); const backuprepo.save([{id:a,text:給下一次創(chuàng)作留一頁白紙}]); if(repository(storage).load()[0].id!a)throw Error(重載失敗); for(const bad of [{version:2,notes:[]}, {version:1,notes:[{id:a,text:1},{id:a,text:2}]}]) { let rejectedfalse;try{decode(bad);}catch{rejectedtrue;} if(!rejected)throw Error(壞備份應(yīng)被拒絕); } console.log(backup);