首页 / 文章 / 为何在2026年JavaScript依然占据主导地位:闭包、异步处理、流以及工具层

为何在2026年JavaScript依然占据主导地位:闭包、异步处理、流以及工具层

闭包、Promise 组合、AbortController、迭代器、流、WeakMap 以及动态导入——此外还包括 Node、TypeScript、React 和 Angular 分别适用的场景。

2113 词

开发团队常忽视的语言特性、当前的采用趋势,以及 Node.js、TypeScript、React、Angular 和 AngularJS 之间的职责划分。

一旦开始开发网络产品,在首个功能上线之前就会出现这些名称:JavaScript、TypeScript、Node.js、React、Angular。学习资料会将它们放在一起,招聘信息也会如此,一个代码仓库里往往同时存在多个相关技术。

这些技术的功能并非可以互相替代的。

明确它们各自的职责,能比“接下来该用哪个框架”这个问题更准确地解答:为何即便团队采用了其他相关工具,JavaScript 依然持续普及。

TypeScript 就体现了这一规律:它改变了代码编写与校验的方式,但其运行时仍属于 JavaScript 的范畴。

系统的持久性源自分销渠道、可灵活调整的语言基础组件,以及能够在系统发展过程中保留原有知识的工具。要实现这一目标,并不需要在所有基准测试中都取得胜利,也不必占据每一个细分市场。

仔细解读采用情况信号

相关数据已于2026年9月13日进行核实。GitHub上的贡献者排名依据的是2025年Octoverse报告中的2025年8月数据,并非2026年9月的实时流量。

客户端采用情况的数据由W3Techs汇总。下文提到的贡献者排名来自2025年GitHub Octoverse报告。

每项指标对应不同的问题。网站上的客户端存在情况并不等同于后端的使用率,贡献者数量也与延迟、招聘需求或生产力无关。人们可以用多种语言进行代码提交,因此将各类数据相加并不能得出唯一的开发者总数。

在2025年8月的排名中,TypeScript位居首位,Python紧随其后,JavaScript则排在第三位。JavaScript的整体使用频率仍在上升。这些数据表明其拥有庞大且不断发展的生态系统,但并非在所有领域都处于领先地位。

分发早在应用代码之前就开始了

浏览器早已将JavaScript交付给用户。

引擎和页面API随浏览器一同提供。Node则将同一种语言部署到另一台主机上,并增添网络功能以及文件系统功能等。随着语言的迁移,可用的主机API也会发生变化。相关参考资料:链接中的参考文献以及Node简介。

这种区分明确了端到端共享同一语言的优势与局限。

一个纯粹的折扣计算器可以在浏览器应用和Node服务之间切换。特定于DOM的代码无法在服务器上运行,而浏览器也无法加载Node的文件系统模块。

跨主机的连贯性有助于解释其持久生命力:工程师无需抛弃已掌握的所有知识即可应用到其他产品领域,不过安全性、性能和运维方面仍需单独学习。

这种语言实际上蕴含的技术内容比人们通常认为的要多。

1. 作为小型可配置工具的闭包

函数是一等值,嵌套函数可从创建时起保留外部局部变量。这种被保留的环境即为闭包。参考:MDN。

想象一下需要为多种显示货币格式化价格的情况:

function createPriceFormatter(locale, currency) {
  const formatter = new Intl.NumberFormat(locale, {
    style: "currency",
    currency,
  });
return amount => formatter.format(amount);
}

const formatUSD = createPriceFormatter("en-US", "USD");
const formatEUR = createPriceFormatter("de-DE", "EUR");
console.log(formatUSD(19.9));
console.log(formatEUR(19.9));

每个返回的函数都会保留自己的格式化器。只需配置一次,即可将该函数传递给任何需要该功能的调用处。

同样的原理也支持注入依赖项、事件处理程序以及可重用的转换函数,而无需强制使用类树结构。闭包还能让对象的生命周期变得清晰可见:只要保留回调函数,捕获的值就会随之保存。

一个旧有的特性至今仍发挥着重要作用。JavaScript的强大表达能力在很大程度上源自普通函数。

2. 将等待操作与异步任务结合

实际应用中常常需要等待各种独立的网络请求,比如用户资料数据、徽章数量、账单汇总等信息。

如果让这些请求依次执行,即前一个请求完成后再启动下一个,就会产生不必要的空闲时间。Promise则能直接体现这种关联关系:

async function fetchJSON(url) {
  const response = await fetch(url);
  if (!response.ok) {
    throw new Error(`HTTP ${response.status}`);
  }
  return response.json();
}

async function loadDashboard() {
  const [account, notifications] = await Promise.all([
    fetchJSON("/api/account"),
    fetchJSON("/api/notifications"),
  ]);
  return { account, notifications };
}

浏览器示例假设存在相同源地址的路由。调用这些函数会发起请求,Promise.all()则用于汇总所有请求的结果。只要有一个请求失败,整个组合 Promise 就会失败。不过各个请求并不会自动被中止,详情可参阅MDN文档。

在讲解并发概念时,可以设想两次独立调用的耗时分别为120毫秒和180毫秒。顺序等待的总时间接近300毫秒;而若让两次调用重叠执行,则在扣除开销前总时间可接近180毫秒。这些时间数值是出于解释需要而设定的,并非实际测量结果。

将CPU密集型任务标记为async并不会将其转移到另一个线程中。较长的同步执行阶段仍会阻塞事件循环。Node的官方指南明确了如何实现高效的异步I/O而不占用整个循环:参见Node的异步操作说明。

3. 通过取消机制终止过时的任务

预输入搜索就是典型的例子。用户每输入一个字符,查询条件就会改变,从而导致之前的搜索结果失效。

主机通过AbortController来实现任务取消功能。若要将调用方的信号与超时机制结合使用,可利用AbortSignal.any():

async function searchProducts(query, callerSignal) {
  const signal = AbortSignal.any([
    callerSignal,
    AbortSignal.timeout(3_000),
  ]);

const response = await fetch(
    `/api/products?q=${encodeURIComponent(query)}`,
    { signal },
  );
  if (!response.ok) {
    throw new Error(`HTTP ${response.status}`);
  }
  return response.json();
}

将调用方传递的控制器信号传入,并在查询数据过期时中止处理。同时仍需处理拒绝情况,以避免使用过时的数据内容。

客户端的中止操作可以取消请求获取过程;而已经接受该请求的服务器则可能继续执行。这些功能是在支持的情况下由主机提供的,并非语言本身的核心语法。相关文档见链接。

从架构层面来看,团队可以指定任务何时结束,而不仅仅是何时开始。

4. 能跳过不必要的操作的惰性迭代器

数组流水线非常实用,但filter().map().slice()可能会处理比实际所需更多的元素,并创建更多中间数组。

迭代器辅助工具采用了不同的方式:

function* productFeed() {
  let id = 1;

  while (true) {
    yield { id, available: id % 2 === 0 };
    id += 1;
  }
}
 const firstThreeAvailableIds = productFeed()
  .filter(product => product.available)
  .map(product => product.id)
  .take(3)
  .toArray();
 console.log(firstThreeAvailableIds); // [2, 4, 6]

生成器可以无限运行,而流水线只会提取足够的数据以返回三个匹配项。

惰性阶段会将任务推迟到有值被请求时再执行。toArray()仍会分配最终的列表,而将现有数组包装起来并不会消除该数组已产生的开销。详情请见MDN。

Iterator.prototype.take()自2025年3月起已在当前所有浏览器中实现Baseline 2025标准;较旧版本的浏览器则需要另行检测。详细信息请见MDN。

这类辅助功能并非推动JavaScript早期流行的原因,它们体现的是在语言已获得广泛普及之后的持续投入。

5. 将内存视为设计要素的流

有时在处理之前就加载整个大文件是浪费资源的。Node流允许通过管道分块进行读取、转换和写入:

// compress.mjs — run with: node compress.mjs
// Requires an existing events.ndjson file.
import { createReadStream, createWriteStream } from "node:fs";
import { createGzip } from "node:zlib";
import { pipeline } from "node:stream/promises";

await pipeline(
  createReadStream("events.ndjson"),
  createGzip(),
  createWriteStream("events.ndjson.gz"),
);

背压机制能在消费者处理速度滞后时减缓生产者的速度,从而降低无限缓冲的风险。实际内存使用情况仍取决于缓冲区大小、数据转换操作以及周边代码。参考:Node streams相关说明。

当数据以渐进方式进入或离开系统时,导出、上传和压缩功能能获得更好效果。流机制扩展了JavaScript服务在运行时层所能处理的能力;它不仅仅是一个关键字而已。

6. 无生命周期管理的WeakMap元数据

UI辅助功能通常需要将数据附加到DOM节点上。

const elementMetadata = new WeakMap();

function rememberValidation(element, result) {
  elementMetadata.set(element, result);
}

function readValidation(element) {
  return elementMetadata.get(element);
}

WeakMap中的键并不会仅仅因为是键就一直存在。当没有其他内容引用该元素时,集合就可以回收它。

这适用于生命周期应与对象相匹配的元数据。它不会修复多余的监听器、计时器或其他引用,且集合触发的时机也未明确指定。按设计,键也是不可枚举的。参考:MDN。

范围虽窄,但回答了一个具体问题:工具如何在不意外拥有对象的情况下记住关于该对象的信息?

7. 与用户行为同步的动态导入

PDF导出功能可能只有在点击“导出”后才会用到。

async function exportReport(report) {
  const { createPdf } = await import("./pdf-exporter.js");
  return createPdf(report);
}

pdf-exporter.js是一个导出createPdf函数的应用模块。该示例展示的是延迟加载机制,而非内置的PDF处理功能。

import()会以异步方式加载。打包工具可能会将其视为分割点,但代码块的布局仍取决于配置设置。延迟加载可以减少初始处理量,但在首次使用时会产生延迟。规范说明:MDN。

那些罕见的功能往往正是进行这种交换的合理依据。在做出决定之前,先测量首次绘制和首次使用的效果。

区分人们容易混淆的层次

为每个概念指定明确的职责可以减少混乱。

权威的参考资料包括TS手册、Node入门指南、React官网、Angular文档以及AngularJS官网。以下建议基于工程实践经验,而非流行度排名。

选择Node.js

当大量时间用于协调网络请求、数据库访问及其他I/O操作时,尤其是团队已经熟悉JavaScript或TypeScript的情况下,应选择Node。

那些对CPU资源要求较高的场景需要精心设计:可以使用工作进程、独立进程或其他服务。事件循环无法解决耗时的同步计算问题。

应将构建工具与生产环境托管分开。用Node编译前端代码并不意味着在线站点就必须使用Node API。

对于2026年9月要推出的新版本服务,只要依赖项允许,Node 24 LTS便是合理的默认选择。官方状态显示当前版本为Node 26,22和24属于LTS版本,而20则已进入生命周期结束阶段。

选择TypeScript

当某个模块的变更可能会导致其他地方出现不兼容的问题时,TypeScript能发挥出色作用。类型功能能够增强编辑器提供的反馈,在运行时之前就发现许多结构错误。

它们不会自动验证API数据内容。断言无法让有问题的外部数据变得可信,检查工具也无法证明支付或授权逻辑的正确性。背景资料:TS手册。

Node 可以去除类型信息,直接运行受支持的 TypeScript 语法。该方式不会进行类型检查,也不会替代完整的工具链;Node 仅说明语法及配置限制。在需要进行检查的情况下,请使用类似 tsc --noEmit 的命令。详情请参阅 Node TS 页面。

选择 React

React 适合需要大量可复用组件以及状态频繁变化的界面,比如账户区域、编辑器、控制面板和结账页面。

它负责处理 UI 层面,而路由、数据访问和部署则属于独立决策范畴。官方资料建议新项目从合适的框架开始,同时也会记录必要的自研方案:React 启动指南。

用 React 构建的前端可以与 Node、Java、Kotlin、Python 或其他后端系统交互。该 UI 库不会强制业务 API 使用 JavaScript。

选择 Angular —— 以及为何 AngularJS 不同

Angular 将路由、表单、依赖注入以及信号等响应式基础组件整合为一个应用程序框架。团队间统一的规范是选择它的有力理由,无需将其限定为仅适用于企业环境。概述:Angular 文档。

AngularJS 是其前身,支持已于2022 年 1 月结束。现代 Angular 是其继任者而非简单升级版,离开 AngularJS 需要进行迁移。到 2026 年,这类技术栈应被归入旧系统维护范畴,而非新项目候选清单。注意:这是旧版系统。

实际可行的组合方案

可将上述内容视为参考起点。团队的专业能力、依赖关系图、无障碍需求、部署限制以及实际性能测试结果都可能改变默认选择。

一种可行的技术组合是TypeScript 加上 React 加上 Node:TypeScript负责类型检查,React用于构建用户界面,Node则用来运行服务器。另一种可行的组合则是将 TypeScript 和 Angular 与 Kotlin API 结合使用。

职责划分才是真正的设计关键问题。

始终具有价值的语言技能

对框架的熟练运用有助于快速实现功能;而对语言及运行时的精通则能解释为何会出现性能下降、代码过时以及变更困难等问题。

闭包有助于明确状态的保留方式;Promise 能够简化协调工作;取消机制可帮助识别过时的操作;迭代器和流则便于实现逐步处理;WeakMap 能明确特定的所有权模式;动态导入则可将模块结构与实际加载过程相连接。

类似的理念在其他领域也存在。JavaScript 的优势在于,它将这一切整合在一个既覆盖浏览器又延伸至服务器工具链的生态系统中。

在评估适用性时,需考量用户可感知的延迟、初始脚本加载量、负载下的内存使用情况、错误率以及变更成本。贡献者数量反映规模,而本地测量数据则体现适用性。

正因如此,即便代码存储在 .ts 或 .tsx 文件中,理解 JavaScript 在 2026 年依然至关重要。