首页 > 人工智能 > 人工智能 > 不用依赖三方库,10个被冷落的原生Web API
现代浏览器的原生能力远比大多数开发者想象的要强悍得多。每一年,Web 平台都在悄无声息地进化出一些颠覆我们开发习惯的新特性。
说来也巧,每次挑选周更的技术主题,往往比写文章本身还要让人纠结。这周我一直在两个极端之间反复横跳:要么去写一个能轻松收割流量的热点爆款,要么就索性一头扎进硬核枯燥的底层技术闭门造车。
但我这次想整点不一样的。它依然需要足够的硬核和实用,但体感要更轻量,不需要你花上三天时间去翻阅浩如烟海的文献。既然我平时最享受的事情就是去探索浏览器的极限、看看在不增加任何外部依赖的前提下网页能走多远,那我最终锁定的这个主题可以说是既简单、又被绝大多数人灯下黑了:那些被严重低估的原生 Web API。
这里面的某些接口可能已经躺在你的日常工作流里了,但我敢打赌,其中绝对有那么几个会让你忍不住揉揉眼睛嘀咕一句:“等等……这玩意居然有原生支持?”
好了,热身结束,咱们直接上干货。
在过去,让网页去直接读写用户设备上的本地文件,这听起来完全是原生桌面软件的特权。要在网页端实现类似的功能,免不了一堆臃肿的上传控件、临时的 Blob 链接,或者各种体验极差的弯路。
这个 API 的出现直接打破了这层结界:
let fileHandle;async function getFile() { // 直接唤起原生的系统文件选择器,解构返回的单文件数组
[fileHandle] = await window.showOpenFilePicker(); // 拿到句柄后,直接对选中的本地文件进行深度操作}在通过标准的权限提示获得用户明确授权后,你的网页应用就能直接读取、甚至直接修改并保存内容到用户的硬盘文件里。没有插件,没有包装层,完全由浏览器原生驱动。
它直接解放了一整套以前在网页端很难做完美的重型体验:
可以直接保存到本地文件的纯网页端代码/文本编辑器。
丝滑无阻的本地数据导入与导出工作流。
完全运行在浏览器内部的开发者辅助工具。
第一次在项目里跑通它的时候,你甚至会有一种隐约的违和感:我们真的可以在浏览器里拥有这么高、这么顺畅的权限吗?但这恰恰是现代 Web 平台演进的终极方向——不用让用户安装任何软件,就能无限抹平网页与原生应用之间的体验鸿沟。
需要注意的是,目前该 API 在基于 Chromium 内核的浏览器(如 Chrome、Edge)上支持得最为完美,Safari 和 Firefox 的支持仍然有限。因此,现阶段最理想的实践是将它作为一种“渐进增强”的优化手段,而不是强依赖。
如果用户同时打开了你应用的四五个浏览器标签页,在过去,这些标签页通常是彼此孤立、老死不相往来的。为了同步它们之间的状态(比如在一个标签页退出了登录,其他标签页也要同步退出),我们不得不去写一些极其脆弱的逻辑,比如去搞轮询、监听
localStorage 的变动,甚至绕回到后端服务器去做消息分发。
而 BroadcastChannel API 的出现,用一种极其优雅的方式把这个问题降维打击了:
// 瞬间创建一个指定频道的通信连接const bc = new BroadcastChannel("test_channel");// 轻松向该频道的所有标签页广播一条消息bc.postMessage("This is a test message.");// 在其他标签页里同步进行监听bc.onmessage = (event) => { console.log(event.data);
};// 搞定之后随时关闭连接bc.close();它允许来自同源(Same-Origin)下的不同浏览器窗口、标签页或者 Iframe 之间,直接在前端建立一条实时的、零延迟的密闭通信管道。
在日常开发中,它的应用场景多到数不过来:
多标签页之间完美、同步地联动退出登录或状态切换。
跨页面实时保持全局凭证(Auth State)的绝对一致。
在一处修改了系统主题或用户偏好,所有页面齐刷刷同步更新 UI。
如果你的用户经常喜欢多开标签页,这个 API 在你接入的那一刻,就会让你的应用显得无比高级。你不再需要去缝补任何怪异的补丁,平台本身就给了一套最干净利落的底层支持。
如果说 BroadcastChannel 是负责页面之间“大喇叭广播”的通讯员,那么 Web Locks API 就是一个默默无闻却掌握大权的“交通指挥官”。它不负责传递消息,只负责协调多端冲突。
它允许同源下的多个标签页、Web Worker 或者 Frame 异步地去申请一把公共锁。在持有锁的期间,确保只有这一个特定的任务在执行,执行完后锁会自动释放。
navigator.locks.request("my_resource", async (lock) => { // 成功抢到锁,开始执行核心任务
await do_something(); await do_something_else(); // 出了这个作用域,锁会被浏览器自动安全释放});我们在写前端应用时经常会遇到一个隐形的性能刺客:当用户多开页面时,每个页面都在后台自顾自地去跑定时任务——高频请求通知、同步本地缓存、触发调度脚本。这不仅极其浪费用户的 CPU 和内存,甚至还可能在前端数据库(如 IndexedDB)里撞车,产生灾难性的竞态条件(Race Conditions)。
Web Locks 就是为了优雅地干掉这个痛点而生的。它在后台精细调度,确保哪怕用户开了十个标签页,某些重型后台任务也只会有其中一个页面在跑,其余的都在排队或静默。
用好它,你的后端服务器能瞬间少掉大量无意义的并发请求,网页由于冲突导致的卡死也会销毁殆尽。这种分布式系统的锁思维,现在直接在浏览器里就能原生享受到。
多年来,在前端面试里有一个经久不衰、甚至有点被问吐了的经典面试题:“在 JavaScript 里,如何实现一个完美的对象深拷贝?”
这个问题的回答往往能直接暴露一个开发者的基本功:是看他懂不懂引用的本质?还是看他会不会熟练运用解构赋值、或者直接一招鲜地掏出
JSON.parse(JSON.stringify(...))?甚至有人会直接说“调一下 Lodash 的
cloneDeep 库”。
而现在,这个长达数年的技术辩论可以彻底画上句号了。
// 创建一个带有极其复杂的循环引用的对象const original = { name: "MDN" };
original.itself = original;// 极其简单地直接进行标准深拷贝const clone = structuredClone(original);console.assert(clone !== original); // ✅ 完全独立的新内存地址console.assert(clone.name === "MDN"); // ✅ 它们拥有相同的值console.assert(clone.itself === clone); // ✅ 循环引用被完美保留,没有死循环!这就是
structuredClone。不需要任何 Hack,不需要去引入哪怕一行的外部工具库,更不需要用 JSON 序列化去玩那种丢失特殊类型的极限体操。
它之所以能成为工业级的终极方案,是因为它原生支持了太多痛点:
能完美拷贝
Map、
Set、
Date、
Blob、
File 以及
ArrayBuffer 等复杂内置对象。
能够极其安全地处理恶心的“循环引用”,绝对不会导致内存溢出或死循环。
规避掉了 JSON 序列化时丢失
undefined、丢掉特定原型的各种暗坑。
唯一的限制是它出于安全和底层设计考虑,不会去拷贝函数(Functions)。但讲道理,在绝大多数真实的业务场景中,深拷贝数据对象时把函数过滤掉,恰恰是最合理的做法。现在,各大主流现代浏览器早就全线支持了这个 API,生产环境完全可以放心大胆地用起来。
性能优化是前端圈永远聊不腻的话题。我们天天在讨论怎么优化配置、怎么跑 Lighthouse 分数、怎么压缩代码。
但很多时候,我们其实陷入了一种盲目的方向焦虑中:我们费尽心思重构的代码 A,到底有没有比原来的代码 B 更快?还是说,我们其实只是在进行一场感动了自己的过度设计(Overengineering)?
// 打上开始时间戳performance.mark("start");// 执行你想测量的核心业务算法// code to measure// 打上结束时间戳performance.mark("end");
performance.measure("calc", "start", "end");// 精准拿到消耗的毫秒数console.log(performance.getEntriesByName("calc"));Performance API 给了你一把极其精准、童叟无欺的度量衡。它不看玄学,不听风评,只用浏览器底层的真实执行毫秒数说话。
在做技术决策时,这一招能帮你省掉无数的口水战:
快速做微观的算法基准测试(Micro-benchmarks)。
实测一下,把这段逻辑开辟到 Web Worker 里去跑,到底有没有带来实质性的速度飞跃。
验证为了提升性能而引入的 WebAssembly,其表现是否真的对得起它带来的架构复杂度。
因为在真实的开发世界里,很多所谓的“天才优化版”,最后测出来往往比直白的原生写法还要慢。尽早用数据给自己做个全方位的体检,能让你少走太多的技术弯路。
这是一个非常小巧、但对现实世界里的设备资源极其温柔的 API。它能让你的网页敏锐地察觉到:当前的标签页是正在被用户盯着看,还是早就被切到了后台、冷落在一个看不见的角落里。
const audio = document.querySelector("audio");let playingOnHide = false;document.addEventListener("visibilitychange", () => { if (document.hidden) { // 用户切走标签页或最小化了浏览器
playingOnHide = !audio.paused;
audio.pause(); // 贴心暂停,节约 CPU
} else if (playingOnHide) { // 用户又切回来了
audio.play(); // 丝滑恢复
}
});我们必须面对一个残酷的现实:用户打开了你的网页,玩了没两分钟,可能就会把它晾在那里,转头去别的标签页里摸鱼摸上几个小时。
如果你的网页毫无防备意识,在失去焦点的这段时间里,它依然会毫无节制地跑定时器、高频轮询后台 API、渲染复杂的 UI 动画,白白在后台烧着用户的 CPU、吃着用户的内存、耗着笔记本的电量。
Page Visibility API 就是用来帮你改掉这些“坏习惯”的:
当页面不可见时,自动暂停重型的视频渲染或大图轮训。
主动降低轮询服务器的频次,甚至直接挂起不必要的网络请求。
大幅削减后台运行时的能耗,让网页在移动端或低配设备上对电池极其友好。
这是一个能让你的应用显得非常有教养的细节优化。你的后端服务器会因为少掉大量无效的后台请求而松一口气,用户的设备也会因为你的克制而更加省电。
如果你在过去开发过响应式的复杂图表、大屏仪表盘或者支持拖帅的弹性布局,你一定被旧时代的做法折磨过:去监听全局的
window.resize、手动写各种节流防抖、死抠
getBoundingClientRect,甚至还要用
setTimeout 去打各种匪夷所思的补丁,仅仅是为了让一个图表组件能随着父容器的变大而自适应撑开。
日剧网
而 ResizeObserver 的诞生,直接把这种痛苦彻底翻篇了。它让我们可以直接去监听 某一个具体 HTML 元素的尺寸变化,而不再是傻傻地只能盯着整个大浏览器视口。
const resizeObserver = new ResizeObserver((entries) => { for (const entry of entries) { if (entry.contentBoxSize) { const contentBoxSize = entry.contentBoxSize[0];
h1Elem.style.fontSize = `${Math.max(1.5, contentBoxSize.inlineSize / 200)}rem`;
pElem.style.fontSize = `${Math.max(1, contentBoxSize.inlineSize / 600)}rem`;
} else {
h1Elem.style.fontSize = `${Math.max(1.5, entry.contentRect.width / 200)}rem`;
pElem.style.fontSize = `${Math.max(1, entry.contentRect.width / 600)}rem`;
}
} console.log("Size changed");
});
resizeObserver.observe(divElem);它彻底解放了“容器查询(Container Queries)”的思维模型:
让 UI 组件真正做到模块化自治——不管被塞到多宽的局部侧边栏里,都能根据自身当前的真实尺寸展示出完美的响应式风貌。
让动态图表、数据可视化报表在尺寸伸缩时,表现得无比平滑、稳定。
消灭掉了所有通过全局
window 监听器伪造出的低效 Hack 逻辑。
它就像是浏览器平台终于看懂了前端开发的辛酸,拍着你的肩膀说:“以前辛苦你了,以后这种测尺寸的琐事,我直接在底层帮你管了。”
如果说上面聊到的 ResizeObserver 是盯着尺寸不放的“守门员”,那么它的亲兄弟 IntersectionObserver 就是专门捕捉“元素有没有露脸”的顶级侦察兵。
它能用极其高效的方式,在后台盯着某一个特定的 HTML 元素。当这个元素滚入或者滚出用户的当前手机屏幕(视口)时,它会立刻发出警报。有了它,你再也不用去写那些极其消耗性能的
window.scroll 监听事件,更不用手动去算各种让人头大的相对坐标。
const options = { root: document.querySelector("#scrollArea"), rootMargin: "0px", threshold: 1.0,
};// 轻轻一行,把复杂的曝光判定交给浏览器底层const observer = new IntersectionObserver(callback, options);这个 API 在现代 Web 开发中几乎承包了半壁江山的刚需场景:
无限滚动(Infinite Scroll): 列表滚到底部的水印时,原生地自动加载下一页,毫无卡顿。
图片与组件懒加载: 只有当图片即将进入屏幕时,才去触发网络请求,帮用户省下大把流量。
高精准度的广告曝光埋点: 只有当广告区域真正 100% 呈现在用户眼前时,才算一次有效的计费曝光。
每一个从传统 scroll 监听、手动死磕 bounding box 的旧时代熬过来的前端,在第一次写完 IntersectionObserver 之后,基本上都有一种“再也回不去了”的痛快感。它不仅写法无比清爽,而且由于是浏览器内部高度优化过的原生进程,其执行性能和丝滑度可以直接甩手写逻辑好几条街。
在很多开发者的潜意识里,
AbortController 似乎只是个专门用来取消
fetch 请求的专用小挂件:
const controller = new AbortController();
fetch(url, { signal: controller.signal });// 在任意你觉得不需要的时刻,果断终止请求controller.abort();但如果你只用它来管网络请求,那可真是太低估它的野心了。在现代 Web 平台的设计哲学里,
AbortController 实际上是一套通用的、底层的“异步撤销机制”。
雷电影院
只要某个原生 API 在设计上支持传入
AbortSignal 信号,它就能在瞬间被你掐断执行。
它能接管的业务边界远远超乎你的想象:
一键解绑事件监听器: 以前用
addEventListener 绑了一堆乱七八糟的全局事件,在组件销毁时,你得一个个手动写对应的
removeEventListener,稍微漏掉一个就是内存泄漏。现在,你只需要给它们传入同一个
signal,最后一行
controller.abort() 就能让它们齐刷刷地瞬间人间蒸发。
精细控制 Stream 流媒体: 在处理长连接、SSE(服务端推送事件)或者文件流读取时,随时刹车。
多任务协同熔断: 你可以用同一个 signal 去同时绑定好几个不相干的网络请求、定时任务和后台计算。只要这把大伞下的任意一个环节出错,你拉响警报,整条链路上的所有异步动作都会瞬间集体刹车。
这种干净、利落的协同撤销能力,能让你面对重型大型项目时游刃有余。它散发着一种只有“成熟、规范、注重可控性”的现代化高阶代码库才具备的优雅高级感。
如果说前面聊到的 Page Visibility 只是在死板地探测“当前的网页标签页有没有被关到小黑屋里”,那么 Idle Detection API 的野心就大得多了——它能直接感知到 屏幕前的那个人类现在到底有没有在干活。
const controller = new AbortController();const signal = controller.signal;
startButton.addEventListener("click", async () => { // 这类强悍的权限必须经过用户的明示授权
if ((await IdleDetector.requestPermission()) !== "granted") { console.error("Idle detection permission denied."); return;
} try { const idleDetector = new IdleDetector();
idleDetector.addEventListener("change", () => { const userState = idleDetector.userState; // 'active' 还是 'idle'
const screenState = idleDetector.screenState; // 'locked' 还是 'unlocked'
console.log(`用户状态变更: ${userState}, ${screenState}.`);
}); await idleDetector.start({ threshold: 60000, // 设定阈值:比如连续 1 分钟毫无动静
signal,
});
} catch (err) { console.error(err.message);
}
});这个 API 能敏锐地捕捉到用户是不是其实正把网页开着,但人早就跑去茶水间泡咖啡了、或者正在会议室开会、甚至是直接把电脑屏幕给锁屏走人了。
摸透了这一层最真实的人性上下文,网页就能做出很多非常聪明的自适应行为:
安全性极高的高级防护: 监测到人离开电脑超过 5 分钟,为了防止敏感商业数据泄露,自动帮用户退出登录并锁死界面。
IM 通讯软件的状态平替: 像 Slack 或者企业微信一样,当用户双手离开键盘一段时间后,自动把头像挂上“暂离/Away”的小黄点。
静默性能优化: 既然人都走了,后台那些高能耗的数据渲染、3D 动效渲染就可以全部休眠,等用户走回座位敲击键盘的瞬间再重新无缝唤醒。
说实话,第一次看到这个特性的时候,多少会觉得有那么一丝丝被窥探的微妙感。但只要你站在端正的 UX 设计和完全透明、公开的授权前提下,它在特定业务场景里能发挥出来的杀伤力绝对是不可替代的。
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/69921010/viewspace-3172266/ ,如需转载,请注明出处,否则将追究法律责任。