你好,我是冴羽。
你可能觉得 querySelector、classList、Promise 这些是浏览器原生的能力,但实际上,它们都是从第三方库"抄"来的。
这就是经典的 Web 平台的演进逻辑:先有社区方案,再有标准化。
本篇带你盘点浏览器 API 中被 JS 库"启发"的 9 个案例。
如果你写 JavaScript 有些年头了,这篇文章会让你回忆起很多往事。
如果你是新人,也欢迎了解一下你每天都在用的这些 API 的前世今生。
1. querySelector 和 querySelectorAll
用 CSS 选择器从 DOM 中选元素,现在看起来理所当然。
但这不是一开始就有的。
在这之前,你得用 getElementById、getElementsByClassName、getElementsByTagName,还要遍历 .children 进行过滤。
Dojo 的 dojo.query 率先实现了这个想法。
然后 jQuery 的 $()把它变成了整整一代 Web 开发者的默认思维模型。
浏览器最终发布了自己的版本:
javascript
const button = document.querySelector(".buy-now");
const allButtons = document.querySelectorAll(".buy-now");
console.log(button.textContent);
allButtons.forEach((btn) => {
console.log(btn.textContent);
});
2. 声明式 UI:popovertarget 和 command
10 年前,Modal 得这样写:
javascript
const button = document.querySelector(".open-modal");
const modal = document.querySelector("#my-modal");
button.addEventListener("click", () => {
modal.classList.add("is-open");
});
Bootstrap 看到这个模式到处重复,就用属性替代了代码:
html
<button data-toggle="modal" data-target="#my-modal">Open</button>
这是因为 Bootstrap 的 jQuery 插件会读取 data-* 属性,帮你完成显示和隐藏。
这样你就不用写任何 JS 代码了。
平台吸收了这个理念:
html
<button popovertarget="my-popover">Open</button>
<div id="my-popover" popover>
<p>Hello from a popover.</p>
<button popovertarget="my-popover" popovertargetaction="hide">Close</button>
</div>
command 通过明确命名动作,把同样的模式扩展到其他内置元素(比如 <dialog>):
html
<button commandfor="my-dialog" command="show-modal">Open dialog</button>
<dialog id="my-dialog">
<p>Hello from a dialog.</p>
<button commandfor="my-dialog" command="close">Close</button>
</dialog>
3. classList
在 classList 之前,操作 className 属性是一件非常复杂的事情。
添加一个 class 你得分割、检查、合并。删除一个 class 你得进行过滤。可烦死我了。
后来 jQuery 的 .addClass()、.removeClass()、.toggleClass() 和 .hasClass() 解决了这些问题。
Web 也发布了自己的标准:
javascript
const button = document.querySelector(".buy-now");
button.classList.add("is-loading");
button.classList.remove("is-disabled");
button.classList.toggle("is-active");
button.classList.contains("is-loading"); // boolean
button.classList.replace("is-loading", "is-success"); // jQuery 中没有
4. 字符串和数组的工具方法
一大批工具方法其实都来自工具库:Underscore、Lodash、MooTools、Prototype.js 这些。
一个不完全的列表:
-
Array.prototype.map、filter、reduce、forEach -
Array.prototype.find、findIndex、some、every -
Array.prototype.includes -
Array.prototype.flat、flatMap -
String.prototype.trim、trimStart、trimEnd -
String.prototype.includes、startsWith、endsWith -
String.prototype.repeat -
String.prototype.padStart、padEnd -
Object.keys、values、entries -
Object.assign
这些方法每一个过去都需要装个库来实现。
5. structuredClone
克隆可谓是 JS 经典的面试题了。
后来大家开始使用 Lodash 的 _.cloneDeep,或者使用一个经典的 JSON.parse(JSON.stringify(obj)) 技巧。
但都有些小问题。
20 年后,终于出了 structuredClone:
javascript
const original = {
name: "Jad",
createdAt: new Date("2026-01-01"),
tags: new Map([["role", "instructor"]]),
nested: { items: [1, 2, 3] },
};
const copy = structuredClone(original);
copy.nested.items.push(4);
console.log(original.nested.items); // [1, 2, 3] (未改变)
console.log(copy.nested.items); // [1, 2, 3, 4]
console.log(copy.createdAt instanceof Date); // true
console.log(copy.tags instanceof Map); // true
6. Promises
当年关于 Promise 的库可多了...... Dojo Deferred、jQuery Deferred、Q、Bluebird 等等。
最终出了统一的 Promises/A+ 规范。
再后来,浏览器实现了 Promise。
如今 Promises 和 async/await 是语言的默认异步模型了:
javascript
async function loadUser(id) {
const response = await fetch(`/api/users/${id}`);
if (!response.ok) {
throw new Error("Failed to load user");
}
return response.json();
}
const user = await loadUser(42);
console.log(user.name);
7. ES Modules
JavaScript 刚出现时竟然没有模块系统。
于是 Node.js 使用 CommonJS 规范,浏览器通过 RequireJS 使用 AMD 规范。
最终浏览器发布了自己的:
javascript
// math.js
export function add(a, b) {
return a + b;
}
export const PI = 3.14159;
javascript
// app.js
import { add, PI } from "./math.js";
console.log(add(2, 3)); // 5
console.log(PI); // 3.14159
8. Temporal
JavaScript 的 Date 对象有问题了 30 年,终于出了 Temporal 彻底替代:
javascript
const birthday = Temporal.PlainDate.from("2026-06-27");
const reminder = birthday.subtract({ days: 7 });
console.log(reminder.toString()); // 2026-06-20
console.log(birthday.toString()); // 2026-06-27 (未改变)
9. Element.closest()
过去,向上遍历 DOM 找到最近的匹配祖先过去意味着手写 while 循环,或者用 jQuery 的 .closest()。
现在它是内置的了。
一时间你可能想不到它的用户,一个典型用例是事件委托:
javascript
document.addEventListener("click", (event) => {
const button = event.target.closest("button.action");
if (!button) return; // 点击不在(或不在内部)匹配的按钮上
const action = button.dataset.action;
console.log("action:", action);
});
使用 closest(),你就可以找出实际点击了哪个按钮。
最后
当然了,不是每个浏览器特性都始于库,很多也直接来自浏览器工程师和标准机构。
但这条特定的路径 ------ 先有社区方案,再有标准化是最健康的演进方式之一。
因为这是一个反馈循环,而不是竞争。
这些库在生产环境中被成千上万的开发者测试,收集 bug 反馈,不断迭代。
那些活下来的模式,最终被标准化进了平台本身。
但不用伤心,平台吸收库不是库"输了"。而是它们成功到变得不必要了。
谨此怀念一下当年写的那么多代码吧,都已经成了历史的尘埃。
我是冴羽,10 年笔耕不辍,专注前端领域,更新了 10+ 系列、300+ 篇原创技术文章,翻译过 Svelte、Solid.js、TypeScript 文档,著有小册《Next.js 开发指南》、《Svelte 开发指南》、《Astro 实战指南》。
欢迎围观我的"网页版朋友圈",每天分享前端知识、AI 干货。