chrome.storage 实战:本地与同步存储
第三章从选型角度把存储区域过了一遍,这一篇进入写代码层面:local 与 sync 两个主力区域的完整用法、get/set 的各种姿势、onChanged 驱动的响应式架构,以及 Service Worker 场景下的异步预载模式。
get 的四种参数形态
chrome.storage 的 get 支持四种入参,返回的都是「键到值」的对象:
// 1. 传 null 或省略:取全部
const all = await chrome.storage.local.get(null);
// 2. 传字符串:取单个
const one = await chrome.storage.local.get("theme"); // { theme: "dark" }
// 3. 传数组:取多个
const some = await chrome.storage.local.get(["theme", "fontSize"]);
// 4. 传对象:带默认值,键不存在时返回默认值
const withDefault = await chrome.storage.local.get({
theme: "light",
fontSize: 14
});
第四种最实用:初始化界面时一次性拿到「有就用没有就用默认」的全部配置,代码干净。remove 清键,clear 清区,getBytesInUse 查容量占用。
sync 的配额与实战纪律
sync 区容量约 100 KB、单项上限 8 KB、条目数有限(QUOTA_BYTES_PER_ITEM 为 8192 字节),且每个键的写入频率受限(MAX_WRITE_OPERATIONS_PER_MINUTE 为 120,MAX_SUSTAINED_WRITE_OPERATIONS_PER_MINUTE 为 30)。设计 sync 数据结构时的纪律:
| 要做 | 不要做 |
|---|---|
| 一个键存整个设置对象 | 每个设置项拆一个键 |
| 防抖合并高频写入 | 输入框每敲一键就 set 一次 |
| 存配置、偏好、书签类小数据 | 存日志、缓存、二进制 |
一个带防抖的保存函数:
let timer;
function saveSettings(settings) {
clearTimeout(timer);
timer = setTimeout(() => {
chrome.storage.sync.set({ settings });
}, 500);
}
sync 的意义在跨设备:用户在公司调好的偏好,回家登录同一账号自动跟上。若用户未开启同步,sync 区退化为 local 行为,代码无需区分。
onChanged:响应式架构的枢纽
设置页改一项、popup 和 content script 立即跟着变,不必广播消息,onChanged 全搞定:
// options.js(设置页)
document.querySelector("#theme").addEventListener("change", (e) => {
chrome.storage.sync.set({ theme: e.target.value });
});
// content.js(页面)
chrome.storage.onChanged.addListener((changes, area) => {
if (area === "sync" && changes.theme) {
document.body.dataset.theme = changes.theme.newValue;
}
});
changes 是 { key: { oldValue, newValue } } 的嵌套结构,旧值新值都在,做撤销、diff 都方便。注意 content script 默认能读 storage(local 与 sync 默认暴露),若你的数据涉及隐私,用 setAccessLevel 收窄到仅扩展上下文可读。
Service Worker 的预载模式
worker 随时休眠,事件处理函数里如果第一步是读 storage,响应就会多一跳。官方文档给出的模式是:启动时预载进缓存,事件到达时等待预载完成:
// background.js
const cache = { count: 0 };
const init = chrome.storage.sync.get().then(items => Object.assign(cache, items));
chrome.action.onClicked.addListener(async (tab) => {
try {
await init; // 首次事件等待预载;之后 init 已 resolved,零开销
} catch (e) { /* 预载失败处理 */ }
cache.count++;
await chrome.storage.sync.set({ count: cache.count });
});
注意这套缓存只存在于 worker 的一次生命周期内,worker 重启后顶层代码重新执行、缓存重建,一致性由 storage 兜底。
什么时候该考虑别的
超出键值模型的需求——存图片、全文检索、几十万条记录——chrome.storage 就不是合适的工具了,转向 IndexedDB,对比与代码见 《存储方案》 篇。存储里存 URL 或文本模式要做匹配时,正则工具箱见 正则实战。
官方文档:https://developer.chrome.com/docs/extensions/reference/api/storage 。所有 storage 操作需要 storage 权限,扩容需要 unlimitedStorage,权限影响见 《权限模型》 篇。
onChanged 与消息机制的取舍
「改设置、全局生效」有两条实现路线,各有适用场景:
| 维度 | onChanged 广播 | 显式 sendMessage |
|---|---|---|
| 耦合度 | 低,写者不知道读者是谁 | 高,点对点 |
| 及时性 | 毫秒级 | 毫秒级 |
| 适合 | 配置类:改完大家各自应用 | 动作类:需要确认执行结果 |
经验法则:状态同步走 onChanged(声明式,新增读者零成本),命令执行走 sendMessage(要返回值和错误处理)。两者经常在同一个扩展里并存:设置变更用 onChanged 分发,而「立即翻译当前页」这类动作用消息。混用时注意 onChanged 的处理函数要保持幂等——同一配置可能被重复应用,UI 层做等值判断跳过无效刷新。
sync 写入失败的观测
sync 区有写入频率限制,超限时 set 会静默失败或抛错(视 API 形态)。上线的扩展建议把关键写入包上错误处理并记录:
async function safeSyncSet(items) {
try {
await chrome.storage.sync.set(items);
return true;
} catch (e) {
// 配额或频率超限:降级写入本地,待下次启动重试
await chrome.storage.local.set({ __pendingSync: items });
return false;
}
}
降级重试的完整实现会在 worker 启动时检查 __pendingSync 并补写 sync。这个模式把「同步失败」从静默数据丢失变成可恢复的延迟,用户无感。
一个设置页的标准结构
把 storage 用法收拢成一个可复用的设置页骨架,几乎所有扩展都用得上:
// options.js
const DEFAULTS = { theme: "light", fontSize: 14, autoRun: true };
async function loadForm() {
const conf = await chrome.storage.sync.get(DEFAULTS);
for (const [k, v] of Object.entries(conf)) {
const el = document.querySelector(`[name="${k}"]`);
if (el) el.type === "checkbox" ? (el.checked = v) : (el.value = v);
}
}
async function saveForm(form) {
const data = {};
new FormData(form).forEach((v, k) => (data[k] = v));
const conf = { ...DEFAULTS, ...data }; // 缺省项补全
await chrome.storage.sync.set({ settings: conf });
}
document.querySelector("form").addEventListener("submit", (e) => {
e.preventDefault();
saveForm(e.target);
});
loadForm();
三个要点:读时用对象参数带默认值;存时整对象单键写入(贴合 sync 的条目配额);HTML 原生表单收集数据,不引框架。这个骨架配合 onChanged 广播,就是完整的多上下文配置系统。
存取的键名模式、缓存键的清洗与校验,写多了就是正则活儿,工具箱见 正则实战。
本章由 WorkBuddy 原创整理,API 行为依据 Chrome 扩展官方文档(Manifest V3)。
