JavaScript 9 个先有库再有 API 的故事

你好,我是冴羽

你可能觉得 querySelectorclassListPromise 这些是浏览器原生的能力,但实际上,它们都是从第三方库"抄"来的。

这就是经典的 Web 平台的演进逻辑:先有社区方案,再有标准化

本篇带你盘点浏览器 API 中被 JS 库"启发"的 9 个案例。

如果你写 JavaScript 有些年头了,这篇文章会让你回忆起很多往事。

如果你是新人,也欢迎了解一下你每天都在用的这些 API 的前世今生。

1. querySelector 和 querySelectorAll

用 CSS 选择器从 DOM 中选元素,现在看起来理所当然。

但这不是一开始就有的。

在这之前,你得用 getElementByIdgetElementsByClassNamegetElementsByTagName,还要遍历 .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.mapfilterreduceforEach

  • Array.prototype.findfindIndexsomeevery

  • Array.prototype.includes

  • Array.prototype.flatflatMap

  • String.prototype.trimtrimStarttrimEnd

  • String.prototype.includesstartsWithendsWith

  • String.prototype.repeat

  • String.prototype.padStartpadEnd

  • Object.keysvaluesentries

  • 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 干货。

相关推荐
kyriewen6 小时前
别再对着 TypeScript 报错发呆了:我把 10 个最常见的红色波浪线翻译成了人话
前端·javascript·typescript
free357 小时前
从 0 实现一个 Tiny JavaScript VM:项目架构拆解
javascript
徐小夕9 小时前
我们开源了一款“框架无关”的思维导图编辑器,3分钟集成到任意系统
前端·javascript·github
PBitW9 小时前
GPT训练我的第三天,明白了应该咋说满分回答!😕😕😕
前端·javascript·面试
像我这样帅的人丶你还9 小时前
Java 后端详解(四):分页与搜索
java·javascript·后端
labixiong9 小时前
还原一个完整符合规范的 Promise(二)
前端·javascript
To_OC10 小时前
万字解析《JS 语言精粹》之第五章:继承 5 大核心精髓(JS 原型核心)
前端·javascript·代码规范
裕波11 小时前
AI 正在重写应用开发。Vue 与 Vite,给出新的答案。
javascript·vue.js
kyriewen13 小时前
折腾了半年 AI 编程工作流,最后发现效率瓶颈是桌上那块屏幕
前端·javascript·ai编程
张元清15 小时前
React useDebounce Hook:给状态和回调做防抖(2026)
javascript·react.js