前端技术进阶公众号文章

别再什么都 npm install 了!这 10 个浏览器原生 API,早就解决了你的问题

每个前端工程师都经历过这样的仪式:遇到一个小问题 → 打开终端 → npm install xxx。

前端技术进阶

公众号 · 3 篇文章

每个前端工程师都经历过这样的仪式:遇到一个小问题 → 打开终端 → npm install xxx。

深拷贝对象,装一个库;图片懒加载,装一个库;无限滚动,再装一个库;多标签页状态同步,再再装一个库。于是 package.json 越来越长,node_modules 越来越黑——直到某天你发现,一个几百 KB 的项目,依赖文件夹躺了 1.2 GB。

图 1:程序员的自嘲——"我绝不提交我没读过的代码",以及 1.2 GB 的 node_modules图 1:每个项目里都有一个"没人读完过"的 node_modules

但有一个事实经常被忽略:Web 平台每年都在悄悄长出新能力。许多你习惯性"装库解决"的问题,浏览器早已提供原生 API:不用安装、不用打包、不用维护升级,也不会出现在你的供应链安全审计报告里。

本文盘点 10 个被严重低估的浏览器 API。其中几个或许是你的日常工具,但我保证,至少有三四个会让你惊呼:“等等,这玩意儿居然存在?”

图 2:一个典型的依赖树手绘图——你安装的每个包,背后还有它自己的依赖图 2:你装的是一个包,进项目的是一个家族


1. structuredClone:深拷贝的终点站

"如何拷贝一个对象?"是一道经典的面试题。答案能暴露很多东西:是否理解引用语义?是否知道展开语法和 Object.assign 只是浅拷贝?是否会用 JSON.parse(JSON.stringify(obj)) 这种会在 Date、Map、Set 和循环引用面前当场爆炸的偏方?还是干脆引入 lodash?

而现在,标准答案只有一行:

const copy = structuredClone(original);

看个极端例子:

const original = {  date: new Date(),  set: new Set([1, 2, 3]),  buffer: new ArrayBuffer(8),};original.self = original; // 循环引用const copy = structuredClone(original); // ✅ 完美深拷贝// JSON.parse(JSON.stringify(original))   // 💥 TypeError 当场爆炸

加分项:支持 Map、Set、Date、Blob、File、ArrayBuffer;自动处理循环引用。注意:不会克隆函数。

兼容性:Chrome 98+ / Firefox 94+ / Safari 15.4+,可放心用于生产。

2. Performance API:别用嘴争论"快不快"

我们谈论性能、安装监控工具、跑 Lighthouse、为优化方案争论不休。但有时你只想回答一个朴素的问题:“A 方案真的比 B 快吗?还是我在过度工程化?”

performance.mark("start");// 待测量的代码performance.mark("end");performance.measure("calc", "start", "end");console.log(performance.getEntriesByName("calc"));

适用场景:微基准测试;验证引入 Worker 或 WASM 是否值得;以及给"优化后的版本"做现实检查——因为有时候,优化版反而更慢。

兼容性:所有现代浏览器,支持极好。

3. Page Visibility API:知道用户何时离开了标签页

document.addEventListener("visibilitychange", () => {  if (document.hidden) {    video.pause(); // 暂停视频    stopPolling(); // 停止轮询  } else {    startPolling();  }});

说句实话:用户会打开你的应用,然后切去别的标签页 20 分钟、2 小时,或者永远。用这个 API 暂停视频、停止轮询、降低 CPU 占用、获得更干净的分析数据——你的后端和用户的电池都会感谢你。

兼容性:所有现代浏览器。

4. ResizeObserver:观察元素,而不只是窗口

const ro = new ResizeObserver((entries) => {  for (const entry of entries) {    const { width, height } = entry.contentRect;    chart.resize(width, height); // 图表随容器自适应  }});
ro.observe(element);

如果你写过响应式组件、图表或仪表盘,大概率写过 window.resize 加 getBoundingClientRect 轮询之类的"诅咒代码"。这个 API 就像是浏览器在说:“放松,交给我。”

兼容性:现代浏览器广泛支持。

5. IntersectionObserver:元素进入视域了吗

ResizeObserver 的兄弟。它回答另一个问题:元素是否进入了视口?

const io = new IntersectionObserver(  (entries) => {    entries.forEach((entry) => {      if (entry.isIntersecting) {        entry.target.src = entry.target.dataset.src; // 懒加载        io.unobserve(entry.target);        loadMoreIfNeeded(entry.target); // 无限滚动      }    });  },  { rootMargin: "200px" },); // 提前 200px 触发
io.observe(element);

无限滚动、图片懒加载、滚动动画的黄金搭档。手工实现过一次无限滚动的人,这辈子都不想再来第二次。

兼容性:所有现代浏览器。

6. AbortController:能取消的不只是 fetch

很多人只在 fetch 里见过它,但它的能力远不止于此:

const controller = new AbortController();// 取消网络请求fetch(url, { signal: controller.signal });// 取消事件监听button.addEventListener("click", onClick, { signal: controller.signal });// 一个 signal,同时取消多个操作controller.abort();

事件监听、流式读取、任何可中断的 API 都能接住这个 signal。一个信号取消一组操作的设计,干净、可扩展,是非常"成熟代码库"的味道。补充两个实用静态方法:AbortSignal.timeout(5000) 一行实现超时,AbortSignal.any([...]) 组合多个取消源。

兼容性:所有现代浏览器。

7. Idle Detection API:标签页开着,但人还在吗

Page Visibility 告诉你标签页是否活跃;Idle Detection 告诉你人是否活跃。

const detector = new IdleDetector();
await detector.start({ threshold: 60_000 }); // 60 秒无操作判定为 idle
detector.addEventListener("change", () => {  console.log(detector.userState); // "active" | "idle"  console.log(detector.screenState); // "locked" | "unlocked"});

用户可能开着你的应用,但人正在泡咖啡或开会。适用场景:自动登出、"离开"状态展示、后台任务降级。是的,你能检测出用户离开了电脑——有点细思极恐,但非常实用。

兼容性:以 Chromium 为主,需要用户授权。

8. BroadcastChannel API:三行代码搞定多标签页通信

const channel = new BroadcastChannel("app");// 任一标签页发出消息channel.postMessage("logout");// 其他标签页接收channel.onmessage = (e) => {  if (e.data === "logout") clearSession();};

登出同步、鉴权状态、共享会话——比 localStorage 搭配 storage 事件的经典 hack 优雅一个时代。在用户"以防万一"开着 5 个标签页的真实世界里,出乎意料地实用。

兼容性:现代浏览器均支持(Safari 15.4+ 加入)。

9. Web Locks API:给标签页加一把锁

BroadcastChannel 的表亲,解决另一类问题:防止多个标签页重复做同一件事。

navigator.locks.request("notifications-lock", async (lock) => {  await fetchNotifications(); // 同一时刻只有一个标签页能进入});

典型场景:只让一个标签页轮询通知,避免后端被自己的前端轰炸;协调共享资源的读写。很有"分布式系统走进前端"的感觉。

兼容性:Chromium 成熟,Firefox / Safari 支持较晚,生产前请核对目标版本。

10. File System Access API:浏览器里的真实文件系统

// 打开文件const [fileHandle] = await window.showOpenFilePicker();const file = await fileHandle.getFile();
// 保存文件const saveHandle = await window.showSaveFilePicker();const writable = await saveHandle.createWritable();await writable.write(content);await writable.close();

Web 编辑器、导入导出工具、面向重度用户的桌面级 Web 应用的核心能力。第一次用它时会有种轻微的"违法感"——"网页里真的允许干这个吗?"允许。

兼容性:Chromium 为主,Firefox / Safari 暂不支持,需做降级方案。


现实检查:兼容性一览

API
Chrome / Edge
Firefox
Safari
structuredClone
✅
✅
✅
Performance API
✅
✅
✅
Page Visibility
✅
✅
✅
ResizeObserver
✅
✅
✅
IntersectionObserver
✅
✅
✅
AbortController
✅
✅
✅
BroadcastChannel
✅
✅
✅
Idle Detection
✅(需授权)
❌
❌
Web Locks
✅
✅(较晚)
✅(较晚)
File System Access
✅
❌
❌

前七个可以放心进生产;后三个(Idle Detection、Web Locks、File System Access)仍带 Chromium 色彩,全量押注前务必查一遍 MDN 兼容性表。

为什么这件事值得认真看待

仅仅知道这些 API 存在,就已经是一种优势。因为依赖的真实成本从来不只是体积:

图 3:一次真实 npm 供应链攻击的影响面——444 个包被投毒、周下载量 4.5 亿+图 3:供应链攻击的影响面——你没用过的包,也可能击穿你的项目

原生浏览器 API 的"保质期"远长于大多数工具库:依赖会随时间累积风险——安全审计、版本升级、breaking change、停止维护的作者;而平台 API 以长期稳定为前提演进,向后兼容是浏览器的生命线。与此同时,那些" tiny helper library "在安全报告里一点都不 tiny:你得为每一个包(包括你不知道自己装了的包)向审计解释一遍。

所以,下次按下 npm install 之前,先问三个问题:

  • 浏览器是不是已经能做了?
  • 这个依赖的真实成本是多少?
  • 它解决的是长期需求,还是一次性需求?

结语

Web 平台的进化速度,远比多数人感知的快。有时候,"新技术"并不是某个框架——而是一个已经安静躺在浏览器里多年的原生能力。框架是伟大的抽象,但浏览器才是真正的运行时;知道何时绕过抽象、直接伸手向平台,是一项资深技能。

你最爱的、没人讨论的 Web API 是哪一个?欢迎在评论区补充你的私藏清单。


参考文章:Sylwia Laskowska, Stop Installing Libraries: 10 Browser APIs That Already Solve Your Problems, dev.to 




前往微信阅读全文

内容来自公众号,可前往微信查看原文。

查看作者的更多文章 →订阅更新、RSS 与阅读指南 →