当前位置: 首页 > news >正文

第七章:数据持久化 —— `chrome.storage` 的记忆魔法

第七章:数据持久化 —— chrome.storage 的记忆魔法

本章目标:掌握 chrome.storage API 的使用,学会数据的增删改查,并为我们的项目创建一个“选项页”(Options Page),让用户可以自定义配置并将其持久化保存。


引子:从“金鱼的记忆”到“大象的智慧”

金鱼的记忆只有七秒(这是一个流传甚广的误解,但比喻很形象)。我们目前的扩展就像这条金鱼。你告诉它:“我讨厌这个网站,以后不要把它分组。” 它点点头,但下次你打开浏览器,它又会热情地把那个网站给你分好组,因为它已经把你昨天说过的话忘得一干二净。

而大象,以其惊人的记忆力著称。它能记住多年的水源地、家族成员,甚至是对它好或坏的人。我们希望我们的扩展能像大象一样,拥有智慧和记忆。

  • 用户设置了一个“白名单”,希望某些网站永远不被自动分组。扩展必须记住这个名单。
  • 用户选择了一个喜欢的主题颜色(比如暗色模式)。扩展必须记住这个偏好。
  • 我们想统计用户总共通过我们的扩展节省了多少次点击。扩展必须记住这个数字,并不断累加。

所有这些“记忆”的需求,都指向了同一个解决方案:数据持久化 (Data Persistence)。即,将数据存储在一个不会因为程序关闭而丢失的地方。

在 Web 开发中,我们有 localStoragesessionStorage。但在扩展开发中,我们有更强大、更合适的选择:chrome.storage API

为什么不用 localStorage

  • 同步阻塞localStorage 是同步的,读写操作会阻塞主线程。在 Service Worker 这种对性能极其敏感的环境中,同步 I/O 是被严格禁止的。
  • 无痕模式下不可用:在无痕窗口中,localStorage 的数据是隔离且临时的,不符合我们持久化的需求。
  • 容量限制:通常有 5MB 的限制。
  • 数据类型有限:只能存储字符串。存储对象需要手动 JSON.stringifyJSON.parse

chrome.storage API 完美地解决了这些问题:

  • 异步非阻塞:所有操作都是异步的,返回 Promise 或接受回调函数,绝不阻塞。
  • 跨设备同步:提供了 sync 存储区,可以(如果用户登录了 Google 账号并开启同步)在用户的不同设备间自动同步数据!
  • 为扩展优化:可以直接存储 JSON 对象,无需手动序列化。对读写频率有优化。
  • 对无痕模式友好

今天,我们将解锁这门“记忆魔法”,并为我们的管家打造一个专属的“大脑皮层”——一个功能完善的选项页。


7.1 chrome.storage API 精讲:你的两个“记忆宫殿”

chrome.storage API 提供了两个主要的存储区域,你可以把它们想象成两个功能不同的“记忆宫殿”。

1. chrome.storage.local —— 本地私人仓库
  • 特点
    • 本地存储:数据只存在于当前用户安装扩展的这台电脑上。不会同步到其他设备。
    • 容量较大:通常有 5MB 的存储空间,对于绝大多数扩展来说绰绰有余。
    • 适用场景:存储那些只对当前设备有意义的数据,或者数据量较大的缓存。例如:用户的本地草稿、大量的历史记录缓存、不需要跨设备同步的复杂配置。
2. chrome.storage.sync —— 云端同步保险箱
  • 特点
    • 自动同步:这是它的核心魅力!只要用户登录了 Chrome/Edge 账号并开启了同步功能,这里的数据就会自动在他们的所有设备间同步。你在公司的电脑上设置了白名单,回到家的电脑上打开扩展,白名单就已经在那里了。
    • 容量较小:为了保证同步效率,sync 存储区的总容量限制较小(约 100KB),并且对单个条目的尺寸(约 8KB)和每分钟的读写次数都有严格限制。
    • 适用场景:存储用户的核心配置、偏好设置、授权 token 等小体积但至关重要的数据。我们的“白名单”和“主题偏好”就非常适合放在这里。

我们项目用哪个? 对于我们的“智能标签页管家”来说,用户的配置(如白名单)属于核心偏好,我们希望它能在用户的所有设备上保持一致。因此,chrome.storage.sync 是我们的首选

核心操作:CRUD (增、查、改、删)

chrome.storage 的操作非常直观,主要就是四个动作。我们以 chrome.storage.sync 为例,local 的用法完全相同。

所有方法都支持两种调用方式:回调函数Promise。在现代 JavaScript 中,使用 async/await 搭配 Promise 会让代码更清晰,我们将主要使用这种方式。

1. 增/改 (Set)
chrome.storage.sync.set(items: object): 用于存储一个或多个项目。它接受一个对象,对象的 key 就是你要存储的数据名,value 就是数据本身。如果 key 已存在,则会覆盖旧值。

// 存储用户的偏好设置
async function saveSettings() {const userSettings = {theme: 'dark',enableNotifications: true,blockedSites: ['facebook.com', 'twitter.com']};await chrome.storage.sync.set({ settings: userSettings });console.log("设置已保存!");
}

2. 查 (Get)
chrome.storage.sync.get(keys: string | string[] | object | null): 用于获取一个或多个项目。它的参数非常灵活。

// 获取单个项目
async function getTheme() {const data = await chrome.storage.sync.get('settings');// 注意:返回的结果总是一个对象// data 的形式是 { settings: { theme: 'dark', ... } }return data.settings ? data.settings.theme : 'light'; // 做个空值判断
}// 获取多个项目
async function getMultiple() {const data = await chrome.storage.sync.get(['settings', 'lastLoginTime']);// data: { settings: {...}, lastLoginTime: 167... }
}// 获取所有项目
async function getAll() {const allData = await chrome.storage.sync.get(null);console.log("所有存储的数据:", allData);
}// 获取时指定默认值 (非常有用!)
async function getSettingsWithDefaults() {const data = await chrome.storage.sync.get({// 如果 'settings' 不存在,则返回这个默认值settings: {theme: 'light',enableNotifications: false,blockedSites: []}});return data.settings;
}

3. 删 (Remove)
chrome.storage.sync.remove(keys: string | string[]): 用于删除一个或多个项目。

async function removeTheme() {// 只删除 settings 对象里的 theme 属性是做不到的,// 需要先 get,修改后再 set。// remove 只能删除顶级的 key。await chrome.storage.sync.remove('settings');console.log("整个 'settings' 对象已被删除。");
}

4. 清空 (Clear)
chrome.storage.sync.clear(): 删除该存储区的所有数据。慎用!

async function clearAllMyData() {await chrome.storage.sync.clear();console.log("云端保险箱已被清空!");
}

理论已就位,是时候为我们的管家建造一个可以让他“冥想”和“学习”的房间了——选项页。


7.2 项目实战:创建功能完善的“选项页” (Options Page)

选项页是一个独立的 HTML 页面,用户可以通过在扩展管理页面点击“扩展程序选项”或者在 Popup 中提供一个链接来访问它。它是我们与用户进行深度交互、允许他们进行复杂配置的主要场所。

我们的目标:创建一个选项页,允许用户管理一个“网站白名单”。在这个名单里的网站,将不会被我们未来的“一键分组”功能所影响。

第一步:创建选项页的 HTML 和 JS 文件
  1. 在项目根目录下,创建一个新的 HTML 文件,命名为 options.html
  2. scripts 文件夹下,创建一个新的 JS 文件,命名为 options.js
📂 my-first-extension/
├── 📄 options.html   <-- 新建
└── 📂 scripts/├── 📄 options.js    <-- 新建├── 📄 background.js└── 📄 content.js
第二步:在 manifest.json 中注册选项页

我们需要告诉浏览器,我们的扩展有一个选项页,它的入口文件是 options.html

打开 manifest.json,添加 options_pageoptions_ui 字段。

{..."action": { ... },"options_page": "options.html","background": { ... },...
}
  • options_page: 这是传统的方式,点击选项时会在一个新标签页中完整打开 options.html
  • options_ui: 这是一个更新的方式,它有两个属性 pageopen_in_tab。如果 open_in_tabfalse(默认),选项页会嵌入在 chrome://extensions 页面中的一个小弹窗里打开。如果为 true,则效果和 options_page 一样。

为了获得最大的空间和灵活性,我们这里使用 options_page

第三步:设计选项页的 UI (options.html)

打开 options.html,我们来搭建一个简单的配置界面。

<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><title>智能标签页管家 - 选项</title><style>/* 这里可以放一些和 popup.html 类似的通用样式 */body { font-family: sans-serif; padding: 20px; max-width: 600px; margin: auto; }h1 { color: #4A90E2; }#whitelist { width: 100%; min-height: 150px; font-size: 14px; padding: 5px; }button { padding: 10px 15px; background-color: #50C878; color: white; border: none; border-radius: 4px; cursor: pointer; }#status { margin-top: 10px; color: green; font-weight: bold; }</style>
</head>
<body><h1>设置</h1><h2>网站白名单</h2><p>在此处添加的网站域名(每行一个),将不会被“一键分组”功能影响。</p><textarea id="whitelist" placeholder="例如:&#10;google.com&#10;github.com"></textarea><button id="save">保存设置</button><div id="status"></div><script src="scripts/options.js"></script>
</body>
</html>

这个界面非常简单:

  • 一个 <textarea> 让用户输入域名列表,每行一个。
  • 一个“保存”按钮。
  • 一个 div 用来显示保存成功或失败的状态。
  • <body> 底部,我们引入了 options.js 脚本。
第四步:编写选项页的交互逻辑 (options.js)

这是本章的核心。我们需要在这个 JS 文件里实现:

  1. 页面加载时,从 chrome.storage.sync 读取已保存的白名单,并显示在 <textarea> 中。
  2. 当用户点击“保存”按钮时,读取 <textarea> 的内容,将其处理成一个字符串数组,然后保存回 chrome.storage.sync

打开 scripts/options.js,写入以下代码:

// scripts/options.js// --- DOM 元素获取 ---
const whitelistTextarea = document.getElementById('whitelist');
const saveButton = document.getElementById('save');
const statusDiv = document.getElementById('status');// --- 功能函数 ---// 1. 保存选项到 chrome.storage
function save_options() {const whitelistValue = whitelistTextarea.value;// 将 textarea 的内容按换行符分割,过滤掉空行,并去除每行首尾的空格const sites = whitelistValue.split('\n').filter(site => site.trim() !== '').map(site => site.trim());chrome.storage.sync.set({whitelistedSites: sites}, () => {// 保存成功后,显示一个状态消息statusDiv.textContent = '选项已保存!';setTimeout(() => {statusDiv.textContent = '';}, 1500); // 1.5秒后自动消失});
}// 2. 从 chrome.storage 读取选项并恢复到页面上
function restore_options() {// 使用带默认值的方式获取,防止第一次使用时出错chrome.storage.sync.get({whitelistedSites: [] // 默认是一个空数组}, (items) => {// 将数组转换回以换行符分隔的字符串,并设置到 textarea 中whitelistTextarea.value = items.whitelistedSites.join('\n');});
}// --- 事件监听 ---// 页面加载完成后,立即恢复之前保存的选项
document.addEventListener('DOMContentLoaded', restore_options);// 点击保存按钮时,执行保存操作
saveButton.addEventListener('click', save_options);

让我们用 async/await 的方式重写一遍,代码会更现代和易读:

// scripts/options.js (async/await 版本)const whitelistTextarea = document.getElementById('whitelist');
const saveButton = document.getElementById('save');
const statusDiv = document.getElementById('status');// 1. 保存选项 (async)
const saveOptions = async () => {const whitelistValue = whitelistTextarea.value;const sites = whitelistValue.split('\n').filter(Boolean).map(s => s.trim());try {await chrome.storage.sync.set({ whitelistedSites: sites });statusDiv.textContent = '选项已保存!';setTimeout(() => { statusDiv.textContent = ''; }, 1500);} catch (error) {statusDiv.textContent = `保存失败: ${error.message}`;statusDiv.style.color = 'red';}
};// 2. 恢复选项 (async)
const restoreOptions = async () => {try {const items = await chrome.storage.sync.get({ whitelistedSites: [] });whitelistTextarea.value = items.whitelistedSites.join('\n');} catch (error) {console.error("恢复选项失败:", error);}
};document.addEventListener('DOMContentLoaded', restoreOptions);
saveButton.addEventListener('click', saveOptions);

代码解读:

  1. restoreOptions 函数:

    • 在页面加载时(DOMContentLoaded)被调用。
    • 它使用 chrome.storage.sync.get 来获取 whitelistedSites 的值。注意我们提供的默认值 { whitelistedSites: [] },这能保证即使用户是第一次打开选项页,items.whitelistedSites 也是一个安全的空数组,而不会是 undefined,从而避免 join 方法报错。
    • 获取到数组后,使用 join('\n') 方法将数组元素用换行符连接成一个字符串,完美地还原回 <textarea> 的格式。
  2. saveOptions 函数:

    • 在用户点击保存按钮时被调用。
    • 它获取 <textarea>value
    • split('\n'): 将字符串按换行符分割成一个数组。
    • filter(Boolean)filter(site => site.trim() !== ''): 一个小技巧,用于过滤掉用户可能输入的多余的空行。
    • map(s => s.trim()): 确保每个域名前后的空格都被去掉。
    • chrome.storage.sync.set: 将处理好的 sites 数组,以 whitelistedSites 为键,保存到云端存储中。
    • 保存成功后,我们在 statusDiv 中显示一个短暂的成功提示,给用户一个明确的反馈。
第五步:部署与验证
  1. 保存所有修改过的文件 (manifest.json, options.html, options.js)。
  2. chrome://extensions 页面,刷新扩展。
  3. 现在,在你的扩展卡片上,你应该能看到一个蓝色的链接 “扩展程序选项”。点击它!
  4. 一个新的标签页会打开,内容就是我们的 options.html
  5. 进行测试
    • 在文本框里输入几个域名,比如 google.comwikipedia.org,每行一个。
    • 点击“保存设置”按钮,你应该能看到“选项已保存!”的提示。
    • 刷新这个选项页。你会发现,你刚才输入的内容依然存在!我们的 restoreOptions 函数起作用了。
    • 关闭这个选项页,甚至关闭整个浏览器再重新打开,然后再次进入选项页。内容仍然在那里!chrome.storage 的持久化魔法成功了!
    • 打开后台 Service Worker 的开发者工具,在 Console 中输入 await chrome.storage.sync.get('whitelistedSites') 并回车,你就能亲眼看到我们存储的数据。

我们已经成功地为我们的扩展创建了一个功能齐全的配置中心,并赋予了它跨会话的记忆能力!


本章总结与展望

在这一章,我们为我们的扩展植入了“记忆”,让它从一个健忘的工具,向一个智能的管家迈出了决定性的一步。

我们学到了:

  1. chrome.storage 的优势:理解了它相比 localStorage 在异步、同步、数据类型支持上的巨大优势。
  2. local vs sync:明确了两个存储区的区别和适用场景。
  3. 熟练掌握了 CRUD 操作:通过 async/await 的方式,我们能流畅地对存储进行增删改查。
  4. 创建了功能性的选项页:我们从零开始,创建了一个允许用户自定义配置并持久化保存的 Options Page。

现在,我们的扩展不仅有了强大的执行能力,还有了可靠的记忆能力。它已经准备好去执行更复杂的、依赖于用户配置的任务了。

在下一章,我们将回到我们的主线功能,并把今天所学的一切付诸实践。我们将要实现那个炫酷的**“一键分组”功能,并且,这个功能会智能地读取我们保存在 chrome.storage 中的白名单**,跳过那些用户不希望被分组的网站。

http://www.dtcms.com/a/321798.html

相关文章:

  • bytearray和bytes
  • 解决flex元素内部文本溢出的问题min-width: 0
  • Pytest项目_day08(setup、teardown前置后置操作)
  • 树和二叉树和算法复杂度
  • 这款MEMS组合导航系统如何实现高性价比?
  • SVM实战:从线性可分到高维映射再到实战演练
  • 智能对讲机是什么?原理、优势、应用场景、发展趋势详解
  • 前端老项目依赖安全漏洞解决
  • 【LLM实战|langchain、qwen_agent】RAG高级
  • 888. 公平的糖果交换
  • YOLO-Count:用于文本到图像生成的可微分目标计数
  • 智慧公厕自动清洁空气环境,节省门店运营成本
  • 什么是SSL证书颁发机构?
  • 北斗变形监测技术应用与案例分析
  • SVM算法实战应用
  • 【开源工具】网络交换机批量配置生成工具开发全解:从原理到实战(附完整Python源码)
  • C++ 标准库容器常用成员函数
  • 04--模板初阶(了解)
  • 【Linux】从零开始:RPM 打包全流程实战万字指南(含目录结构、spec 编写、分步调试)
  • 【探展WAIC】从“眼见为虚”到“AI识真”:如何用大模型筑造多模态鉴伪盾牌
  • 惯量时间常数 H 与转动惯量 J 的关系解析
  • uniapp开发微信小程序遇到富文本内容大小变形问题v-html
  • 【谷歌 SEO】排查页面未索引问题:原因与解决方案
  • 页面tkinter
  • CALL与 RET指令及C#抽象函数和虚函数执行过程解析
  • 锂电池保护板测试仪:守护电池安全的核心工具|深圳鑫达能
  • 深度学习里一些常用的指标(备份)
  • 常见数据结构介绍(顺序表,单链表,双链表,单向循环链表,双向循环链表、内核链表、栈、队列、二叉树)
  • 浅析线程池工具类Executors
  • 客户端攻击防御:详解现代浏览器安全措施