首页
/ Web-Dev-For-Beginners 浏览器扩展作业实战:选一个 API,用表单 + LocalStorage 打造真正有用的插件

Web-Dev-For-Beginners 浏览器扩展作业实战:选一个 API,用表单 + LocalStorage 打造真正有用的插件

2026-09-06 14:29:15作者:牧宁李

这篇指南围绕 Web-Dev-For-Beginners 课程「5-browser-extension」模块的第二部分作业「Adopt an API」展开:你需要自选一个外部 API,亲手构建一个能解决真实问题的浏览器扩展,完整覆盖表单输入、API 集成、LocalStorage 持久化与错误处理四大能力。读完本文后,你将掌握从 API 选型、扩展规划、编码实现到测试打磨的完整作业流程,并能参照仓库中的 Carbon Trigger 参考实现(start 骨架代码solution 完整实现)逐项对照自己的代码,确保满足作业评分量表中的每一项要求。

Carbon Trigger 浏览器扩展运行效果:输入 API key 与地区代码后展示地区碳排放结果

浏览器开发者工具 Application 标签下的 Local Storage 面板,用于查看扩展持久化的 apiKey 与地区数据

一、作业背景:它在整个浏览器扩展项目中的位置

在 Web-Dev-For-Beginners 中,浏览器扩展模块(5-browser-extension/README.md)以「Carbon Trigger」为主线项目:一个可在 Edge、Chrome、Firefox 上运行的扩展,调用 CO2 Signal API 查询指定地区的用电碳强度,帮助用户判断当前是否适合进行高耗电动作(比如在碳强度高时暂缓烘干衣物)。整个模块分三课:

  1. 了解浏览器如何工作、如何部署扩展;
  2. 本课的作业上下文——表单、API 调用与 LocalStorage(见 2-forms-browsers-local-storage/README.md);
  3. 后台任务与性能分析。

本课正课教你把「静态表单」变成「有真实数据、有记忆的动态工具」;而 assignment.md 则要求你脱离课程指定的 CO2 Signal API,自选一个 API 完成一个独立扩展。作业原文的四步流程是:选择 API → 规划扩展 → 构建扩展 → 测试打磨。下文逐步展开,并在每一步给出仓库中的源码级参照。

二、Step 1:选择你的 API

作业要求从公开的免费公共 API 列表(即业界知名的 public-apis 清单仓库)中挑选一个。对初学者友好的类别与推荐如下(来自作业原文):

类别 推荐 API 用途
娱乐 Dog CEO API 随机狗狗图片
天气 OpenWeatherMap 实时天气数据
名言 Quotable API 励志语录
新闻 NewsAPI 当前头条
趣味知识 Numbers API 关于数字的冷知识

结合作业给出的入门建议(Getting Started Tips),选型时应注意:

  1. 从简单开始:优先选择不需要复杂认证(甚至无需 API key)的 API,降低首次集成的门槛;
  2. 先读文档:彻底理解所选 API 的端点(endpoint)、请求参数与响应结构,再动手写代码;
  3. 先画 UI:编码前先在纸上草图扩展界面,明确表单字段、结果区与错误区的位置;
  4. 频繁测试:增量开发,每加一个功能就测试一次;
  5. 假设会失败:始终假定 API 调用可能失败,并提前规划错误处理。

一个判断 API 是否适合初学者的实操标准:能否用一次无参或单参的 GET 请求拿到 JSON?响应里是否有清晰的字段说明?是否公开文档了错误码与速率限制?这些也是正课「Challenge」部分要求你在调研任意浏览器 API 时必须回答的问题(它解决什么真实问题、如何处理错误与边缘情况、存在哪些安全考量、跨浏览器支持如何)。

三、Step 2:规划扩展——四个必答问题

作业要求在编码前书面回答以下四个问题。它们分别对应实现中的关键设计决策:

规划问题 决定什么 参照 Carbon Trigger 项目的答案示例
你的扩展解决什么问题? 核心功能与展示内容 查询所在地区电网实时碳强度,辅助用户安排高耗电活动
目标用户是谁? 界面复杂度与文案语气 关注可持续用电的普通消费者
要在 LocalStorage 里存什么数据? 持久化键名与数据形态 apiKey 与地区代码(字符串,key-value 形式)
如何处理 API 失败或速率限制? 错误提示与重试策略 try/catch 捕获,向用户展示友好错误信息而非崩溃

以仓库中 Carbon Trigger 的实际设计为例(见 solution/src/index.js):setUpUser 函数在保存凭证后立即进入「loading 可见、错误区清空、清除按钮可见」的 UI 状态,再发起首次 API 调用;init 函数则根据 LocalStorage 中是否已有数据,把界面切换成「首次用户看表单」或「老用户直接看结果」两条分支。你的规划文档中应能对应到这样具体的状态流转描述。

四、Step 3:构建扩展——必备功能与代码要求

4.1 作业规定的必备功能(Required Features)

作业原文要求你的扩展必须包含:

  • 表单输入(form inputs),覆盖 API 所需的全部参数;
  • 带恰当错误处理的 API 集成;
  • 使用 LocalStorage 保存用户偏好或 API key;
  • 干净、响应式的用户界面;
  • 加载状态(loading states)与用户反馈。

代码层面的硬性要求(Code Requirements):

  • 使用现代 JavaScript(ES6+)特性;
  • 对 API 调用使用 async/await
  • 使用 try/catch 做错误处理;
  • 添加解释代码意图的有意义注释;
  • 保持一致的代码格式化。

4.2 从骨架代码理解实现结构

仓库提供了带编号注释的骨架文件 start/src/index.js,用 //1//6 标记了代码应放置的六个位置。这正是「按正课分段补齐代码」的工作方式:

//1
// form fields        // 表单与结果区的 DOM 引用
// results divs

//6
//call the API        // 调用 API 并渲染结果

//5
//set up user's api key and region

//4
// handle form submission

//3 initial checks    // init():读 LocalStorage 决定初始 UI 分支

//2
// set listeners and start app   // 绑定事件并启动

对应正课讲解的完整调用链是:document.querySelector 拿到 .form-data.region-name.api-key.loading.errors.result-container 等元素引用 → form.addEventListener('submit', ...)clearBtn.addEventListener('click', ...) 绑定事件并立即调用 init()handleSubmite.preventDefault() 阻止默认提交(否则页面会刷新、丢失全部 JS 状态)→ setUpUser 写入 LocalStorage 并触发 displayCarbonUsage → API 返回后更新多个 UI 元素。

4.3 LocalStorage:给扩展持久记忆

LocalStorage 是这条链路的核心持久化机制,其关键特性(正课原文归纳):

  • 数据在浏览器会话之间持久保存(跨会话,区别于 sessionStorage);
  • 以 key-value 形式存取,getItem() / setItem() 操作,键不存在时 getItem() 返回 null——这正是 init() 判断「首次用户还是老用户」的依据;
  • 数据在关闭浏览器、重启电脑后依然保留;
  • 提供无需网络延迟的即时访问。

仓库参考实现中的读写代码(solution/src/index.js):

// set up api key and region
const setUpUser = async (apiKey, region) => {
	localStorage.setItem('apiKey', apiKey);
	localStorage.setItem('region', region);
	loading.style.display = 'block';   // 显示加载状态
	errors.textContent = '';            // 清空历史错误
	clearBtn.style.display = 'block';  // 显示清除按钮
	//make initial call
	displayCarbonUsage(apiKey, region);
};

//initial checks
const init = async () => {
	//if anything is in localStorage, pick it up
	const storedApiKey = localStorage.getItem('apiKey');
	const storedRegion = localStorage.getItem('region');

	if (storedApiKey === null || storedRegion === null) {
		//if we don't have the keys, show the form
		form.style.display = 'block';
		results.style.display = 'none';
		loading.style.display = 'none';
		clearBtn.style.display = 'none';
		errors.textContent = '';
	} else {
		//if we have saved keys/regions in localStorage, show results when they load
		results.style.display = 'none';
		form.style.display = 'none';
		displayCarbonUsage(storedApiKey, storedRegion);
		clearBtn.style.display = 'block';
	}
};

const reset = async (e) => {
	e.preventDefault();
	//clear local storage for region only
	localStorage.removeItem('region');
	init();   // 回到首次用户分支,重新展示表单
};

你可以在 DevTools(F12)的 Application 标签下展开 Local Storage 面板验证扩展确实写入了 apiKey 与地区键值(本文开头第二张截图即该面板示例)。

安全提示(继承自正课原文):在生产环境中把 API key 存进 LocalStorage 存在风险——任何页面 JS 都能读取它。作为学习目的没有妨礙,但真实应用应将敏感凭证放在服务端安全存储。另外,浏览器扩展拥有与普通网页相互隔离的独立 LocalStorage,这提供了安全隔离、避免与其他网站冲突。

4.4 API 调用:fetch 与错误处理

正课给出的依赖零库版本使用原生 fetch,要点是 response.ok 检查 + try/catch 兜底(节选自 2-forms-browsers-local-storage/README.md 的完整函数):

async function displayCarbonUsage(apiKey, region) {
	try {
		const response = await fetch(
			`https://api.co2signal.com/v1/latest?countryCode=${region}`, {
				method: 'GET',
				headers: {
					'auth-token': apiKey,
					'Content-Type': 'application/json'
				}
			});

		// Check if the API request was successful
		if (!response.ok) {
			throw new Error(`API request failed: ${response.status}`);
		}

		const data = await response.json();
		const carbonData = data.data;
		const carbonIntensity = Math.round(carbonData.carbonIntensity);

		// Update the user interface with fetched data
		loading.style.display = 'none';
		form.style.display = 'none';
		myregion.textContent = region.toUpperCase();
		usage.textContent = `${carbonIntensity} grams (grams CO₂ emitted per kilowatt hour)`;
		fossilfuel.textContent = `${carbonData.fossilFuelPercentage.toFixed(2)}% (percentage of fossil fuels used to generate electricity)`;
		results.style.display = 'block';
	} catch (error) {
		console.error('Error fetching carbon data:', error);
		// Show user-friendly error message
		loading.style.display = 'none';
		results.style.display = 'none';
		errors.textContent = "Sorry, we couldn't fetch data for that region. Please check your API key and region code.";
	}
}

而仓库的 solution 版本 改用 axios(start/package.json 中声明了 axios 依赖),并多了一个值得注意的防御点——在渲染前显式校验关键字段是否存在:

const data = response?.data?.data;
// ✅ Validate required data before using
if (data?.carbonIntensity == null || data?.fossilFuelPercentage == null) {
	throw new Error('Missing carbon intensity or fossil fuel data');
}

两种写法都印证了作业「错误处理」评分项的分层思路:网络层失败(try/catch 捕获)、HTTP 层失败(response.ok / axios 的 reject)、数据层缺失(字段校验)三层都要有兜底,最后统一收敛为用户可理解的错误文案。

4.5 扩展工程结构:manifest、入口页与构建

你的自选 API 扩展需要与 Carbon Trigger 相同的工程骨架。仓库中 start/dist/manifest.json 是 MV3 扩展清单的范例:

{
	"manifest_version": 3,
	"name": "My Carbon Trigger",
	"version": "0.1.0",
	"host_permissions": ["<all_urls>"],
	"background": {
		"service_worker": "background.js"
	},
	"action": {
		"default_popup": "index.html"
	}
}

action.default_popup 指向扩展弹窗页面,即承载表单与结果区的 start/dist/index.html——可以看到页面预留了 <!--form area--><!--result area--> 两个注释锚点,正好对应正课中「表单区 / 结果区」的 UI 双分支状态;host_permissions 则授权扩展发起跨域 API 请求。

构建与运行流程(来自 start/README.md):

npm install
npm run build

package.jsonbuild 脚本即 webpackwatch 脚本为 webpack --watch(开发时可热更新),环境要求 node >= 18npm >= 9。构建产物落在 dist 目录。在 Edge 中安装:打开右上角「三点」菜单进入 Extensions 面板,选择「Load Unpacked」,选中 dist 文件夹即可加载扩展;Chrome 对应 chrome://extensions 开启开发者模式后「加载已解压的扩展程序」。

五、Step 4:测试与打磨

作业给出的测试清单及对应的验证手段:

测试项 验证方法
用各种输入测试扩展 合法/空/超长/非法格式的地区码或参数,覆盖表单校验(required 属性依赖浏览器原生验证,正课特别强调这一点)
处理边缘情况(无网络、非法 API 响应) 断开网络模拟失败路径;用错误 key 触发 401/403,确认走 catch 分支且展示友好文案而不是白屏
确保浏览器重启后扩展仍正常 重启后依赖 LocalStorage 中的 apiKey/地区值走 init() 的老用户分支,自动重新拉取数据
添加用户友好的错误信息 错误文案应提示用户下一步动作(如「请检查 API key 与地区代码」),而非堆砌技术细节

正课还配了一个进阶挑战(GitHub Copilot Agent Challenge),可作为打磨阶段的验收标准:为 API 调用增加指数退避重试、调用前校验地区码、带进度指示的加载动画、带 30 分钟过期时间戳的 LocalStorage 缓存、历史数据展示,以及完整的 JSDoc 注释。这些要求恰好也是下一节的加分项。

六、Bonus Challenges:把扩展推向更高水平

作业列出的五项加分挑战,以及实现时可以参考的仓库线索:

  1. 接入多个 API 端点:如 CO2 数据之外增加区域列表端点(Electricity Map 的 zones 端点在 start/README.md 中被用于查询地区代码),让用户不必手输代码;
  2. 实现数据缓存以减少 API 调用:LocalStorage 存 {value, timestamp},读取时先判断 30 分钟内的缓存是否有效——即正课 Copilot 挑战中的缓存模式;
  3. 为常用操作添加键盘快捷键:利用 WebExtensions 的 commands 能力绑定快捷键唤起扩展或触发查询;
  4. 数据导出/导入:将 LocalStorage 中的偏好序列化为 JSON 供用户下载,再支持粘贴/文件导入恢复;
  5. 用户自定义选项:在表单中暴露更多偏好项(单位、刷新频率、阈值告警色等),全部落入 LocalStorage。

七、提交要求与评分量表

7.1 提交物清单

  1. 一个能成功集成所选 API 的可运行浏览器扩展
  2. 一份 README,说明:选了这个 API 及原因、如何安装与使用、需要哪些 API key 或前置配置、扩展运行中的截图;
  3. 干净且有注释的代码,遵循现代 JavaScript 实践。

7.2 评分量表(Rubric,完整继承自作业原文)

维度 优秀 (90-100%) 熟练 (80-89%) 发展中 (70-79%) 入门 (60-69%)
API 集成 无缝的 API 集成,具备全面的错误处理与边缘情况管理 API 集成成功,带基础错误处理 API 可用但错误处理有限 API 集成存在严重问题
代码质量 干净、注释完善的现代 JavaScript,遵循最佳实践 良好的代码结构与充足注释 代码可运行但组织待改进 代码质量差、注释极少
用户体验 打磨精致的界面,出色的加载状态与用户反馈 良好的界面与基础用户反馈 能正常运作的基础界面 用户体验差、界面令人困惑
LocalStorage 高水平的 LocalStorage 运用,含数据校验与管理 正确实现关键功能的 LocalStorage 基础的 LocalStorage 实现 使用极少或错误
文档 全面的 README,含搭建步骤与截图 良好文档,覆盖大部分要求 基础文档,缺失部分细节 文档差或完全缺失

对照这张量表自查时,可以逐行映射到第四节各小节:API 集成对应 4.4 的三层错误兜底;代码质量对应骨架代码的注释与格式化要求;用户体验对应 loading/错误/结果三态切换;LocalStorage 对应 4.3 的存取与校验;文档对应 7.1 的 README 清单。

八、延伸阅读:仓库中可继续深入的文件

作业最后给出的学习资源主题(对应 MDN 官方文档):WebExtensions 扩展开发文档、Fetch API 使用指南、Window/localStorage 存储教程、JSON 解析与处理。带着上面的规划问题与评分量表开始,选一个你真正想用起来的 API,你的第一个「有记忆、有数据」的浏览器扩展就完成了。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
898
5.82 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
921
1.84 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.8 K
1.02 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
531
596
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.02 K
519
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.36 K
1.46 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
548
391