ES2026 落地了什么?两个真上线的特性 + 两个被毙的,一次讲清楚

Temporal API、管道运算符、Record & Tuple、using 语法。但查完 TC39 官方记录后发现,四个里只有两个真的进了 ES2026。我看了不少文章,基本都是把四个混在一起写。实际上有的上线了,有的没有通过。


一、先搞清楚:ES2026 到底包含什么?

ES2026 是 ECMAScript 的 2026 年版本,由 TC39 委员会制定。一个特性要进 ES2026,必须在 2026 年的 TC39 会议上达到 Stage 4(最终阶段)

简单说下 TC39 的流程:

复制代码
Stage 0 → 提出想法
Stage 1 → 值得进一步探索
Stage 2 → 初步规范,草案阶段
Stage 3 → 规范完整,等待实现和反馈
Stage 4 → 最终确认,进入下一年度 ECMAScript

一个特性从 Stage 0 到 Stage 4,短则一两年,长则近十年。Temporal API 花了整整 9 年。

2026 年 3 月的 TC39 会议上,一批提案达到 Stage 4,正式进入 ES2026。其中跟前端日常开发关系最密切的是这两个:

特性 Stage 4 时间 核心价值
Temporal API 2026 年 3 月 原生日期时间处理,彻底替代 Date
using(显式资源管理) 2025 年 5 月 自动资源清理,告别 try/finally 样板代码

而另外两个经常被提到的特性:

特性 当前状态 说明
Record & Tuple ❌ 2025 年 4 月被撤回 停在 Stage 2,被毙了
管道运算符 |> ❌ 仍在 Stage 2 官方明确说"今年不可能到 Stage 4"

下面逐个讲。


二、Temporal API:Date 的继任者,等了 9 年

2.1 为什么 Date 非被替代不可

如果你写过前端,大概率被 Date 折磨过。举个最经典的坑:

js 复制代码
const date = new Date(2026, 6, 22);
// 你以为创建的是 7 月 22 日?
// ✅ 没错,确实是 7 月 22 日。但月份是从 0 开始的------6 代表 7 月
// 第一次遇到这个设计的人,多半会骂一句

月份从 0 开始这个设计,源自 Java 1.0 时代的 java.util.Date,JS 原样抄了过来。这个"历史包袱"导致无数 off-by-one bug。

但月份 0-index 只是冰山一角。Date 真正的问题是缺少类型区分------它把"日期""时间""时刻""时段"全部混在一个对象里:

js 复制代码
// 你想表示"2026年7月22日"这个纯日期(不含时区)
const d1 = new Date(2026, 6, 22);
// 但 d1 内部存的是 UTC 毫秒时间戳,一旦跨时区,显示就不一样了
// 在东八区显示 2026-07-22 00:00:00
// 在零时区显示 2026-07-21 16:00:00 ← 差了一天!

// 你想表示"下午3点"这个纯时间
// Date 做不到,只能 new Date() 然后设置小时
const d2 = new Date();
d2.setHours(15, 0, 0);
// 但它仍然带了一个日期和时区

再比如日期运算:

js 复制代码
// "30天后是几号?"------用 Date 写,得这样
const now = new Date();
const future = new Date(now);
future.setDate(now.getDate() + 30);
// 如果跨了月底或年底,setDate 会自动进位...大部分时候
// 但你确定它能处理闰年、闰秒、夏令时吗?每次写都得查一遍

// 两个日期相差多少天?
const diff = (d2 - d1) / (1000 * 60 * 60 * 24);
// 这个除法能记住吗?毫秒→秒→分→时→天,四层转换

说实话,每次写日期相关的代码,我都要对着文档确认一遍。而 Temporal API 就是为了彻底解决这些问题而生的。

2.2 Temporal 的核心设计:类型分离

Temporal 最关键的设计理念是类型分离------不同概念用不同类型表示,不混在一起。

自己先看一眼这张全景图,对整体有个感知:

scss 复制代码
┌─────────────────────────────────────────────────────────────────┐
│                      Temporal API 类型全景                        │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  ┌──────────────┐  ┌──────────────┐  ┌───────────────────────┐  │
│  │   Instant    │  │  ZonedDateTime│  │                       │  │
│  │  时间线上的   │  │  带时区的      │  │  Duration             │  │
│  │  精确时刻     │  │  日期时间      │  │  时间长度             │  │
│  │  (UTC纳秒)   │  │  (如东八区)    │  │  (如 "2天3小时")     │  │
│  └──────────────┘  └──────────────┘  └───────────────────────┘  │
│                                                                 │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │           Plain 类型族(不含时区信息)                    │    │
│  │  ┌────────────┐ ┌────────────┐ ┌────────────────────┐   │    │
│  │  │ PlainDate  │ │ PlainTime  │ │ PlainDateTime      │   │    │
│  │  │ 2026-07-22 │ │ 15:30:00   │ │ 2026-07-22 15:30   │   │    │
│  │  │ 纯日期     │ │ 纯时间      │ │ 日期+时间(无时区)  │   │    │
│  │  └────────────┘ └────────────┘ └────────────────────┘   │    │
│  │  ┌────────────────────────────────────────────────┐     │    │
│  │  │ PlainYearMonth │ PlainMonthDay                 │     │    │
│  │  │ 2026-07        │ 07-22                         │     │    │
│  │  │ 年月           │ 月日(无年份)                  │     │    │
│  │  └────────────────────────────────────────────────┘     │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

核心逻辑就是**"含时区"用 ZonedDateTime,"不含时区"用 Plain 系列**。

下面逐个类型的介绍。

2.3 PlainDate 和 PlainTime:纯日期和纯时间

最常用的两个类型。当你只关心"几月几号"或"几点几分",不关心时区时,用这俩。

js 复制代码
// 纯日期:2026年7月22日
const date = Temporal.PlainDate.from({ year: 2026, month: 7, day: 22 });
console.log(date.toString()); // "2026-07-22"
// 👆 月份是从 1 开始的!终于正常了

// 纯时间:下午3点30分
const time = Temporal.PlainTime.from({ hour: 15, minute: 30 });
console.log(time.toString()); // "15:30:00"

// 也可以用字符串创建
// 效果等价于上方代码效果
const date2 = Temporal.PlainDate.from("2026-07-22");
const time2 = Temporal.PlainTime.from("15:30:00");

注意第一个细节:Temporal 的月份从 1 开始。就这一条,就能少出无数 bug。

2.4 Instant:时间线上的精确时刻

Instant 表示 UTC 时间线上的一个精确时刻,和 Unix 时间戳是同一个概念,只是精度更高(纳秒级)。

js 复制代码
// 当前时刻
const now = Temporal.Now.instant();
console.log(now.epochMilliseconds); // 1721630400000(和 Date.now() 一样)

// 从时间戳创建
const instant = Temporal.Instant.fromEpochMilliseconds(1721630400000);

// 从 ISO 8601 字符串创建
const instant2 = Temporal.Instant.from("2026-07-22T08:00:00Z");
// 👆 末尾的 Z 表示 UTC

Instant 的核心特点:它只有一个值,不受时区影响。无论你在地球哪个角落,同一个 Instant 表示的是同一个时刻。时区只影响"显示",不影响"值"。

2.5 ZonedDateTime:带时区的完整日期时间

这是 Temporal 中功能最全的类型------日期、时间、时区全都有。

js 复制代码
// 从字符串创建(带时区)
const meeting = Temporal.ZonedDateTime.from(
  "2026-07-22T15:00:00+08:00[Asia/Shanghai]"
);
console.log(meeting.year);   // 2026
console.log(meeting.month);  // 7
console.log(meeting.day);    // 22
console.log(meeting.hour);   // 15
console.log(meeting.timeZoneId); // "Asia/Shanghai"

// 转换到其他时区看看
const tokyo = meeting.withTimeZone("Asia/Tokyo");
console.log(tokyo.hour); // 16(东京比上海快1小时)
const ny = meeting.withTimeZone("America/New_York");
console.log(ny.hour); // 3(纽约和上海差12小时,夏令时期间差12小时)

举个实际场景:你在上海,要和一个在纽约的同事开会,约好了"北京时间下午 3 点"。用 Date 写时区转换代码很痛苦,用 Temporal 就很自然:

js 复制代码
// 北京时间 7月22日下午3点
const shanghaiTime = Temporal.ZonedDateTime.from(
  "2026-07-22T15:00:00+08:00[Asia/Shanghai]"
);

// 纽约同事看到的是几点?
const nyTime = shanghaiTime.withTimeZone("America/New_York");
console.log(`纽约时间: ${nyTime.year}-${nyTime.month}-${nyTime.day} ${nyTime.hour}:${nyTime.minute}`);
// 纽约时间: 2026-7-22 3:0
// 👆 凌晨3点... 纽约同事可能不太开心

2.6 Duration:时间长度

Duration 表示一段时间长度,比如"2 天 3 小时"。它和 Instant/PlainDate 不同------Duration 不是某个时间点,而是时间点之间的"距离"。

js 复制代码
// 创建一个 Duration
const duration = Temporal.Duration.from({ days: 2, hours: 3 });
console.log(duration.toString()); // "P2DT3H"
// 👆 ISO 8601 持续时间格式:P 开头,T 分隔日期和时间

// 日期运算:30天后是几号?
const today = Temporal.Now.plainDateISO();
const future = today.add(Temporal.Duration.from({ days: 30 }));
console.log(future.toString()); // 自动处理月底、年底进位

// 两个日期相差多少天?
const d1 = Temporal.PlainDate.from("2026-07-01");
const d2 = Temporal.PlainDate.from("2026-07-22");
const diff = d1.until(d2);
console.log(diff.toString()); // "P21D"(21天)

// 反过来也行
const diff2 = d2.since(d1);
console.log(diff2.toString()); // "P21D"

还记得前面用 Date 写日期运算时那个 / (1000 * 60 * 60 * 24) 吗?Temporal 直接 until() / since() 就完事了。

2.7 日期比较

Temporal 的比较也很直观:

js 复制代码
const d1 = Temporal.PlainDate.from("2026-07-01");
const d2 = Temporal.PlainDate.from("2026-07-22");

console.log(d1.equals(d2)); // false
console.log(d1.compare(d2)); // -1(d1 < d2)
// compare 返回 -1 / 0 / 1,和 Array.prototype.sort 的比较器一致

// 实用场景:判断是否在某个范围内
const start = Temporal.PlainDate.from("2026-07-01");
const end = Temporal.PlainDate.from("2026-07-31");
const target = Temporal.PlainDate.from("2026-07-22");

const isInRange = start.compare(target) <= 0 && target.compare(end) <= 0;
console.log(isInRange); // true

2.8 格式化和解析

Temporal 支持 ISO 8601 格式,也可以用 toLocaleString 做本地化:

js 复制代码
const zdt = Temporal.ZonedDateTime.from(
  "2026-07-22T15:30:00+08:00[Asia/Shanghai]"
);

// ISO 8601 格式
console.log(zdt.toString()); // "2026-07-22T15:30:00+08:00[Asia/Shanghai]"

// 本地化格式
console.log(zdt.toLocaleString("zh-CN", {
  dateStyle: "full",
  timeStyle: "long"
}));
// "2026年7月22日星期三 中国标准时间 下午3:30:00"

// 只取日期部分
console.log(zdt.toPlainDate().toLocaleString("zh-CN"));
// "2026/7/22"

2.9 浏览器支持情况

截至 2026 年 7 月:

运行时 支持情况
Chrome 144+ ✅ 原生支持
Firefox 139+ ✅ 原生支持
Safari ⚠️ Technology Preview 中,正式版未支持
Node.js 24+ ⚠️ 需 --harmony-temporal flag
Node.js 22- ❌ 不支持

过渡期可以使用 polyfill:

bash 复制代码
npm install @js-temporal/polyfill
js 复制代码
// 在不支持原生 Temporal 的环境中
import { Temporal } from "@js-temporal/polyfill";
// 用法和原生 API 完全一致

现在就可以在项目中用起来。可以使用 polyfill 兜底,等浏览器全面支持后去掉 polyfill 就行。API 不会变,代码不用改。


三、using:自动资源清理,告别 try/finally

3.1 它解决了什么问题

先看一段熟悉的代码。你在读一个文件流,读到一半出错了:

js 复制代码
async function readFile(url) {
  const response = await fetch(url);
  const reader = response.body.getReader();

  try {
    while (true) {
      const { done, value } = await reader.read();
      if (done) break;
      // 处理数据...
      // 😱 如果这里抛错了?
    }
  } finally {
    reader.releaseLock(); // 必须在 finally 里手动释放
  }
}

这个模式的问题不在于"难写",而在于容易忘finally 里的清理代码,平时没人注意它,直到线上出事------文件句柄泄漏、数据库连接打满、定时器没清除------才发现忘了写。

C# 有 using,Python 有 with,Java 有 try-with-resources,都是解决同一个问题。现在 JS 也有了。

3.2 using 的基本用法

using 声明一个变量,在作用域结束时自动调用它的 [Symbol.dispose]() 方法

js 复制代码
{
  using resource = {
    name: "数据库连接",
    [Symbol.dispose]() {
      console.log(`清理: ${this.name}`);
    }
  };
  console.log("使用资源中...");
  // 即便这里抛错了,dispose 也会被调用
}
// 输出:
// 使用资源中...
// 清理: 数据库连接

注意:using 只能在有花括号 {} 的作用域内使用 ------代码块、函数体、for 循环里都行,但顶层不行。

3.3 自定义可释放对象

任何对象只要有 [Symbol.dispose]() 方法,就能用 using 管理。举个实际例子------数据库连接:

js 复制代码
class DBConnection {
  constructor(url) {
    this.url = url;
    console.log(`连接数据库: ${url}`);
  }

  query(sql) {
    console.log(`执行查询: ${sql}`);
    return { rows: [] };
  }

  [Symbol.dispose]() {
    console.log(`关闭数据库连接: ${this.url}`);
    // 这里放真正的关闭逻辑
  }
}

// 使用
{
  using db = new DBConnection("mysql://localhost:3306/mydb");
  db.query("SELECT * FROM users");
  db.query("SELECT * FROM orders");
  // 离开作用域时,自动调用 [Symbol.dispose]()
}
// 输出:
// 连接数据库: mysql://localhost:3306/mydb
// 执行查询: SELECT * FROM users
// 执行查询: SELECT * FROM orders
// 关闭数据库连接: mysql://localhost:3306/mydb

不用写 try/finally,不用记着调 close()------离开花括号就自动清理。

3.4 await using:异步资源清理

有些资源的清理是异步的(比如关闭网络连接、刷新缓冲区)。这时候用 await using

js 复制代码
class FileHandle {
  constructor(path) {
    this.path = path;
  }

  async write(data) {
    console.log(`写入 ${data} 到 ${this.path}`);
  }

  async [Symbol.asyncDispose]() {
    console.log(`异步关闭文件: ${this.path}`);
    await flushBuffer(this.path); // 假设有个刷新缓冲区的异步操作
  }
}

async function processFile() {
  await using file = new FileHandle("/tmp/data.txt");
  await file.write("hello");
  await file.write("world");
  // 离开作用域时,自动 await [Symbol.asyncDispose]()
}
// 输出:
// 写入 hello 到 /tmp/data.txt
// 写入 world 到 /tmp/data.txt
// 异步关闭文件: /tmp/data.txt

await usingusing 的区别就一个:前者等 [Symbol.asyncDispose]() 执行完再继续,后者同步调 [Symbol.dispose]()

3.5 DisposableStack:批量管理多个资源

当多个资源需要一起清理时,用 DisposableStack

js 复制代码
{
  using stack = new DisposableStack();

  const db = stack.use(new DBConnection("mysql://localhost/mydb"));
  const cache = stack.use(new RedisConnection("redis://localhost"));
  const file = stack.use(new FileHandle("/tmp/output.txt"));

  // 正常使用
  db.query("SELECT 1");
  cache.set("key", "value");
  file.write("result");

  // 离开作用域时,按入栈的逆序释放:
  // 1. 关闭文件
  // 2. 关闭 Redis
  // 3. 关闭数据库
}

逆序释放是个重要设计------资源之间可能有依赖关系,后创建的依赖先创建的,所以释放时要反过来。

DisposableStack 还有几个方法:

js 复制代码
{
  using stack = new DisposableStack();

  // use():添加一个已有 [Symbol.dispose] 的资源
  stack.use(resourceWithDispose);

  // adopt():给一个没有 [Symbol.dispose] 的对象加上清理逻辑
  stack.adopt(reader, (r) => r.releaseLock());

  // defer():添加一个纯清理动作(不需要关联资源)
  stack.defer(() => console.log("收尾工作"));

  // move():把当前 stack 的资源转移到新 stack
  const newStack = stack.move();
}

3.6 一个实战场景:SSE 流式请求

举个前端常见的场景------SSE(Server-Sent Events)流式请求。用 using 管理连接的关闭:

js 复制代码
async function streamChat(url, onMessage) {
  const response = await fetch(url);
  const reader = response.body.getReader();
  const decoder = new TextDecoder();

  // 用 DisposableStack 管理 reader
  using stack = new DisposableStack();
  stack.adopt(reader, (r) => r.cancel());

  try {
    while (true) {
      const { done, value } = await reader.read();
      if (done) break;
      const text = decoder.decode(value, { stream: true });
      onMessage(text);
    }
  } finally {
    // stack 离开作用域时自动 cancel reader
    // 这里 finally 其实都不用写,因为 using 已经兜底了
  }
}

// 调用
await streamChat("/api/chat", (msg) => {
  console.log("收到:", msg);
});
// 函数结束后,reader 自动被 cancel,连接关闭

3.7 SuppressedError:清理时的错误处理

如果资源清理本身抛了错,而代码块内也有错误,using 会用 SuppressedError 把两个错误都包起来:

js 复制代码
{
  using resource = {
    [Symbol.dispose]() {
      throw new Error("清理时出错了");
    }
  };
  throw new Error("业务代码出错了");
}
// 抛出: SuppressedError
// - error: "清理时出错了"(最近抛出的)
// - suppressed: "业务代码出错了"(被掩盖的)

这个设计确保你不会丢失任何一个错误信息。

3.8 浏览器支持情况

运行时 支持情况
Chrome 134+ ✅ 原生支持
Firefox 134+ ✅ 原生支持
Safari ❌ 暂不支持
Node.js 24+ ✅ 支持
Babel ✅ 可编译

和 Temporal 不同,using 在 Node.js 24+ 上已经原生支持,不需要 flag。

3.9 哪些场景适合用 using

说实话,using 不像 Temporal 那样"每个项目都要用"。它更适合有明确生命周期管理的场景

场景 适合度 说明
文件/流操作 ⭐⭐⭐⭐⭐ reader、writer、file handle
数据库连接 ⭐⭐⭐⭐⭐ 连接池、事务
WebSocket / EventSource ⭐⭐⭐⭐ 需要手动关闭的连接
定时器 ⭐⭐⭐ setTimeout/setInterval 可包装
性能计时 ⭐⭐⭐ performance.mark/measure 可包装
普通业务逻辑 没有清理需求就别硬用

不要为了用而用。如果你的代码里没有"打开→使用→关闭"的模式,using 对你没有价值。


四、被毙的 Record & Tuple:为什么被撤回?

4.1 它原本要解决什么问题

JS 有个经典"痛点"------对象和数组是引用类型,{} !== {}

js 复制代码
// 两个"看起来一样"的对象,不相等
const a = { x: 1, y: 2 };
const b = { x: 1, y: 2 };
console.log(a === b); // false

// 数组也一样
console.log([1, 2, 3] === [1, 2, 3]); // false

这导致用对象做 Map 的 key 时很麻烦,深比较需要手动遍历或用 lodash 的 isEqual

Record & Tuple 提案想引入值类型 的数据结构------#{x: 1, y: 2}#[1, 2, 3],它们按值比较:

js 复制代码
// 提案中的语法(已撤回,无法使用)
const a = #{ x: 1, y: 2 };
const b = #{ x: 1, y: 2 };
console.log(a === b); // true(如果提案通过的话)

4.2 为什么被撤回

2025 年 4 月 14 日的 TC39 会议上,经过讨论后达成共识:撤回该提案。核心原因是委员会无法就"是否给 JS 添加新的原始类型"达成一致。

具体争议点包括:

  1. 引擎实现成本高------新的原始类型意味着所有 JS 引擎(V8、SpiderMonkey、JSC)都要修改 GC、内存布局、性能优化路径,工程量巨大
  2. 与现有生态的兼容问题 ------JSON.parse 返回的是普通对象,不是 Record;第三方库的 API 都是基于引用类型设计的
  3. Object.freeze 已经存在------虽然不能实现值比较,但能保证不可变性,部分场景够用
  4. 深度相等比较的替代方案已经够多 ------structuredClone + 自定义比较函数、lodash 的 isEqual、immutable.js 等

4.3 现在用什么替代

Record & Tuple 被毙了,但"值比较"的需求还在。目前可用的方案:

方案一:Object.freeze(简单场景)

js 复制代码
// 冻结对象,防止修改
const config = Object.freeze({ x: 1, y: 2 });
// config.x = 100; // 静默失败(严格模式抛错)

// 但 freeze 不解决比较问题
const a = Object.freeze({ x: 1 });
const b = Object.freeze({ x: 1 });
console.log(a === b); // 仍然 false

方案二:JSON 序列化做 key(需要值比较时)

js 复制代码
// 用 JSON 字符串作为 Map 的 key
const cache = new Map();
const key = JSON.stringify({ x: 1, y: 2 });
cache.set(key, "result");

// 取的时候也序列化
const key2 = JSON.stringify({ x: 1, y: 2 });
console.log(cache.get(key2)); // "result" ✅

方案三:第三方库(大型项目)

js 复制代码
// Immutable.js
import { Map } from "immutable";
const a = Map({ x: 1, y: 2 });
const b = Map({ x: 1, y: 2 });
console.log(a.equals(b)); // true ✅

// 或用 Ramda
import { equals } from "ramda";
console.log(equals({ x: 1 }, { x: 1 })); // true ✅

方案四:关注 Composites 提案(未来)

Record & Tuple 撤回后,社区在讨论一个替代方案------Composites 提案。它不引入新的原始类型,而是给现有对象提供"值语义"的比较能力。目前还在非常早期的讨论阶段,没有任何规范文本,可以关注但别等。


五、还没到的管道运算符

5.1 它要解决什么问题

函数嵌套多了,可读性就很差:

js 复制代码
// 一堆函数嵌套,从内往外读
const result = format(
  sanitize(
    parse(
      readFile("data.txt")
    )
  )
);

管道运算符 |> 把嵌套变成链式调用,从左往右读

js 复制代码
// 提案中的语法(仍是 Stage 2,未进入 ES2026)
const result = readFile("data.txt")
  |> parse(%)
  |> sanitize(%)
  |> format(%);
// % 是占位符,表示前一步的结果

这个语法借鉴自 Elixir、F#、Elm 等语言,在这些语言里已经验证了可读性很好。

5.2 为什么还没进 ES2026

管道运算符从 2017 年就提出了,至今 8 年仍停在 Stage 2。核心争议是占位符的语法

  • Hack 风格 (当前草案):x |> f(%),用 % 做占位符
  • F# 风格 (早期方案):x |> f,默认传给第一个参数
  • Smart Mix 风格:不用占位符,但只支持单参数

委员会在几种风格之间来回讨论,一直没能达成共识。2024 年的 issue 里官方说了一句:"管道运算符今年不可能到 Stage 4"

5.3 现在用什么替代

方案一:链式调用(如果函数支持)

js 复制代码
// 很多库已经支持链式调用
const result = _
  .chain(readFile("data.txt"))
  .thru(parse)
  .thru(sanitize)
  .thru(format)
  .value();
// lodash 的 chain 模式

方案二:中间变量(最朴实但最有效)

js 复制代码
const content = readFile("data.txt");
const parsed = parse(content);
const sanitized = sanitize(parsed);
const result = format(sanitized);
// 多几行代码,但谁都能看懂

方案三:自定义 pipe 函数

js 复制代码
// 一个极简的 pipe 实现
const pipe = (...fns) => (x) => fns.reduce((v, f) => f(v), 0);

const transform = pipe(parse, sanitize, format);
const result = transform(readFile("data.txt"));

说实话,方案二和方案三已经能覆盖 90% 的场景。管道运算符的"语法糖"价值大于"功能"价值------它不会让你做到之前做不到的事,只是让代码更好读。


六、总结:ES2026 速查

来一张知识图谱,把全文串起来:

vbnet 复制代码
┌─────────────────────────────── ES2026 ─────────────────────────────┐
│                                                                     │
│  ✅ 真上线的                                                        │
│  ┌───────────────────────┐    ┌───────────────────────┐            │
│  │    Temporal API       │    │   using (ERM)         │            │
│  │                       │    │                       │            │
│  │  · PlainDate/Time     │    │  · using + dispose    │            │
│  │  · Instant            │    │  · await using        │            │
│  │  · ZonedDateTime      │    │  · DisposableStack    │            │
│  │  · Duration           │    │  · SuppressedError    │            │
│  │  · 月份从 1 开始 ✅   │    │  · 替代 try/finally   │            │
│  │  · 替代 Date          │    │                       │            │
│  │  · Chrome 144+, FF 139+│   │  · Chrome 134+, FF 134+│           │
│  │  · Node 24+ (flag)    │    │  · Node 24+ (原生)    │            │
│  └───────────────────────┘    └───────────────────────┘            │
│                                                                     │
│  ❌ 没进的                                                          │
│  ┌───────────────────────┐    ┌───────────────────────┐            │
│  │   Record & Tuple      │    │   管道运算符 |>       │            │
│  │                       │    │                       │            │
│  │  · 2025/04 被撤回     │    │  · 仍在 Stage 2       │            │
│  │  · 原因:新原始类型    │    │  · 争议:占位符语法   │            │
│  │    争议太大           │    │  · 替代:中间变量 /   │            │
│  │  · 替代:freeze /     │    │    自定义 pipe 函数   │            │
│  │    JSON.stringify /   │    │                       │            │
│  │    Immutable.js       │    │                       │            │
│  └───────────────────────┘    └───────────────────────┘            │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

总之就是:

  1. Temporal API 是 ES2026 最重要的特性------Date 终于有继任者了,月份从 1 开始,类型分离,时区处理不再痛苦。现在就可以用 polyfill 上项目。
  2. using 是锦上添花------有资源管理的场景用起来很爽,没有也别硬用。Record & Tuple 被毙了,管道运算符还在路上,别被错误信息误导。

七、面试题尝鲜

Q1:Temporal API 和 Date 的主要区别是什么?

答: 主要区别有三点:

  1. 类型分离------Date 把日期、时间、时刻混在一个对象里,Temporal 拆成 PlainDate(纯日期)、PlainTime(纯时间)、Instant(精确时刻)、ZonedDateTime(带时区日期时间)等
  2. 月份从 1 开始------Date 的月份是 0-indexed(0-11),Temporal 是 1-indexed(1-12)
  3. 不可变 ------Date 的方法会修改原对象(如 setHours()),Temporal 的所有操作都返回新对象

Q2:using 和 try/finally 有什么区别?

答: usingtry/finally 清理模式的语法糖,但有三点优势:

  1. 声明即管理 ------using 声明的同时就绑定了清理逻辑,不需要单独写 finally
  2. 不会忘 ------using 在作用域结束时自动调用 [Symbol.dispose](),不需要手动调用清理方法
  3. 错误不丢失 ------如果清理时也抛错,using 会用 SuppressedError 保存被掩盖的错误,而 try/finally 中后抛的错会覆盖先抛的

Q3:Record & Tuple 提案为什么被撤回?

答: 2025 年 4 月 TC39 会议上达成共识撤回,核心原因是无法就"是否给 JS 添加新的原始类型"达成一致。主要争议包括:

  1. 引擎实现成本高------所有 JS 引擎需修改 GC 和内存布局
  2. 与现有生态不兼容------JSON.parse 返回普通对象而非 Record
  3. Object.freeze 和第三方库(Immutable.js)已部分覆盖需求
  4. 社区在讨论替代方案 Composites 提案,但仍在早期阶段

Q4:管道运算符 |> 解决什么问题?为什么还没进 ES2026?

答: 管道运算符解决函数嵌套的可读性问题------把 f(g(h(x))) 变成 x |> h(%) |> g(%) |> f(%),从左往右读更自然。仍未进入 ES2026 的原因是 TC39 对占位符语法存在分歧(Hack 风格用 % vs F# 风格不用占位符),2017 年提出至今仍在 Stage 2。

Q5:用 Temporal 实现"计算两个日期之间相差多少天"。

答:

js 复制代码
const d1 = Temporal.PlainDate.from("2026-07-01");
const d2 = Temporal.PlainDate.from("2026-07-22");
const diff = d1.until(d2);
console.log(diff.days); // 21
// 或用 since
const diff2 = d2.since(d1);
console.log(diff2.days); // 21

相比用 Date:const diff = (d2 - d1) / (1000 * 60 * 60 * 24),Temporal 不需要手算毫秒转换,且能正确处理跨月、跨年、夏令时等边界情况。

相关推荐
IT知识分享1 小时前
WebP转JPG开发经验:从格式解码到本地批量转换的实践
javascript·python·图片转换
肖志-AI全栈1 小时前
JavaScript 快速排序:从 pivot、双指针到分治思想
开发语言·javascript·排序算法
To_OC3 小时前
拼路径读文件总踩坑?我把 Node 的 path 和 fs 彻彻底底捋了一遍
javascript·后端·node.js
自然 醒3 小时前
记录We码开发者工具的一个bug
前端·javascript·bug
葡萄城技术团队3 小时前
HTML元素单元格:用自定义 CellType 扩展 SpreadJS 的显示能力
前端·javascript·html
shawxlee14 小时前
vue3在public下封装config.js自定义配置动态数据,可在打包后直接修改,方便后端部署及后续维护
前端·javascript·经验分享·vue·团队开发·js·项目优化
kyriewen14 小时前
我给前端项目的接口请求套了6层保护——才发现以前一直在裸奔
前端·javascript·面试
郝亚军16 小时前
如何安装webstorm、Node.js和vue CLI
前端·javascript·vue.js
an3174217 小时前
6MB 组织树大文件性能优化全流程
前端·javascript·vue.js