编程 ECMAScript 2024–2026 深度实战:Iterator Helpers 干掉手写循环、Set 自带集合运算、using 终结回调地狱——现代 JS 这些神兵你用对了吗?

2026-08-16 02:42:40 +0800 CST views 6

ECMAScript 2024–2026 深度实战:Iterator Helpers 干掉手写循环、Set 自带集合运算、using 终结回调地狱——现代 JS 这些神兵你用对了吗?

一、背景介绍:为什么 2024–2026 这批特性值得你专门花一晚上啃

很多人对 JavaScript 新特性的印象还停留在 let/const、箭头函数、Promiseasync/await 那几波「改变了写代码姿势」的大更新上。后面的更新看起来「每年都有,但好像都是些小打小闹」。

这是一个危险的错觉。

从 2024 年开始,TC39 的年度发布节奏(ECMAScript 现在基本是一年一个正式版)进入了一个非常务实的阶段:不再追「颠覆性语法糖」,而是补齐工程里天天踩的坑——资源释放、集合运算、惰性迭代、安全的正则拼接、结构化分组。这些东西单独看每个都不性感,但你把它凑齐了看:一个合格的数据处理管道,过去要写 80 行还容易漏 finally;现在 20 行,且不会有资源泄漏。

我做过一个不太严谨的统计:在我们团队的代码库里,2022 年的 PR 中,因为「忘了在 finally 里关连接/释放锁/清定时器」导致的线上事故,以及因为「手搓 Set 交集差集写反了」导致的数据错误,加起来占了「非业务逻辑 bug」的相当比例。这些 bug 的共同点是:编译器不会报错,Code Review 也经常看不出来,只有跑出问题那天才暴露。

而这几年 TC39 干的事,本质上就是把「正确的做法」变成「语言的默认做法」。当 using 能自动替你调 dispose,当 Set.intersection 是引擎内置的、经过严格测试的哈希实现,你就少了一个自己写错的机会。

本文不堆砌特性清单,而是按「问题 → 旧写法为什么烂 → 新写法怎么写 → 在真实架构里怎么组合」的节奏,把下面这些 2024–2026 的核心特性彻底讲透:

  • Iterator Helpers(ES2025)Iterator.prototype.map/filter/take/drop/flatMap/reduce/toArray/...,以及 ES2026 的 Iterator.from / Iterator.range
  • Set / Map 集合运算(ES2025)union / intersection / difference / symmetricDifference / isSubsetOf / isSupersetOf / isDisjointFrom
  • RegExp.escape(ES2025):终结「用户字符串拼进正则」的安全灾难
  • 显式资源管理(ES2024)usingSymbol.disposeDisposableStackAsyncDisposableStack,把 RAII 真正带进 JS
  • Float16Array(ES2025):半精度浮点在 ML / 图形里的真实价值
  • import attributes / JSON 模块(ES2025)import x from './y.json' with { type: 'json' } 的安全语义
  • Object.groupBy / Map.groupBy / Array.fromAsync / Promise.withResolvers(ES2024):分组与异步构造的官方解法

每一节都有可运行的代码。读完你应该能直接在下一个项目里用上至少三四个,并少写一堆 try/finally


二、核心概念:逐个拆解,从「为什么」到「怎么写」

2.1 Iterator Helpers:终于不用为了「链式处理」先把整个数组物化出来

痛点

过去你想对一串数据做 map → filter → slice → reduce,标准做法只有两条路:

// 路一:数组方法链,每一步都生成一个新数组
const result = bigArray
  .map(x => expensiveTransform(x))
  .filter(x => x.isValid)
  .slice(0, 100)
  .reduce((acc, x) => acc + x.value, 0);

问题在哪?bigArray 有 100 万条,map 先物化出 100 万个元素的数组,filter 又物化一个,slice 再一个。三次全量遍历 + 三次全量内存分配。你只需要前 100 条,却为 100 万条付了三次内存和遍历的代价。

路二是手写 for 循环 + break,性能是对的,但代码丑、不可复用、不可组合。

新写法:迭代器上直接挂方法,全部惰性

ES2025 给 Iterator.prototype(注意是所有迭代器,不只是数组)加了一整套方法。它们不返回数组,返回新的迭代器,所以是惰性的:

function* naturals() {
  let i = 0;
  while (true) yield i++; // 无限序列,但没关系,下面会 take 截断
}

const result = naturals()
  .map(n => n * n)          // 惰性:n => n*n
  .filter(n => n % 2 === 0) // 惰性:偶数
  .take(5)                  // 惰性:只取前 5 个
  .toArray();               // 此时才真正执行

console.log(result); // [0, 4, 16, 36, 64]

关键点:

  • .map / .filter / .flatMap 是惰性的,只有 .toArray() / .reduce() / for...of 真正拉动执行。
  • .take(n) / .drop(n) 相当于以前要手写的「截断」和「跳过头部」,现在是一等公民。
  • .flatMap 比数组的 flatMap 更强:它对迭代器逐个展开,不会一次性物化。
  • 无限迭代器也能安全操作,因为惰性 + take 组合天然支持流式处理。

再看一个真实场景——分页抓数据,只抓到满足条件为止:

async function* fetchPages(api) {
  let cursor = null;
  do {
    const { items, next } = await api.list({ cursor });
    yield* items;
    cursor = next;
  } while (cursor);
}

// 惰性:只要前 50 条「已激活」的用户,哪怕后面还有 10 万页也不多抓
const activeUsers = await fetchPages(userApi)
  .filter(u => u.status === 'active')
  .take(50)
  .toArray();

ES2026 的甜点:Iterator.fromIterator.range

ES2026 把两个极常用的辅助方法扶正了。过去你想对一个「可迭代对象」调 .map,必须先用 arr[Symbol.iterator]()[...iter],现在:

// 普通对象 + 迭代器包装
Iterator.from([1, 2, 3]).map(x => x * 2).toArray(); // [2, 4, 6]

// 数字区间,再也不用自己写 range 函数了
Iterator.range(1, 11)              // 半开区间 [1, 11)
  .filter(n => n % 2 === 0)
  .toArray();                      // [2, 4, 6, 8, 10]

Iterator.range(0, 100, 10);        // 带步长:[0, 10, 20, ... 90]

心智模型:把迭代器当成「不会物化的数组」。只要你不 .toArray(),它就只是一份「待执行的配方」。


2.2 Set 集合运算:别再手搓交集差集了,引擎实现的比你写的快还正确

痛点

「求两个集合的差集」这类需求在去重、权限比对、数据同步里无处不在。手搓的常见写法:

// 手写差集 A - B
function difference(a, b) {
  const sb = new Set(b);
  const out = new Set();
  for (const x of a) if (!sb.has(x)) out.add(x);
  return out;
}

写起来不难,但:每次都要 new Set(b)、每次都要自己保证语义正确、碰到「集合里有对象/NaN」时还可能出诡异 bug(NaN !== NaN,但 Set 认为两个 NaN 相等,手写循环却不会)。

新写法:Set 自带全套集合运算(ES2025)

const a = new Set([1, 2, 3, 4]);
const b = new Set([3, 4, 5, 6]);

a.union(b);                 // Set {1, 2, 3, 4, 5, 6}
a.intersection(b);          // Set {3, 4}
a.difference(b);            // Set {1, 2}        —— a 有 b 没有
b.difference(a);            // Set {5, 6}
a.symmetricDifference(b);   // Set {1, 2, 5, 6}  —— 只在其中一个里的

a.isSubsetOf(b);            // false
a.isSupersetOf(b);          // false
a.isDisjointFrom(b);        // false(有交集)

这些都是原地不变、返回新 Set,且不修改原集合。实现基于哈希,复杂度是 O(min(size)) 级别。

真实场景——两个版本的权限清单 diff:

const grantedV1 = new Set(['read', 'write', 'admin', 'billing']);
const grantedV2 = new Set(['read', 'write', 'audit', 'billing']);

const revoked = grantedV1.difference(grantedV2);    // Set {'admin'}
const added   = grantedV2.difference(grantedV1);    // Set {'audit'}
const overlap = grantedV1.intersection(grantedV2);  // 保留的权限

一句话:凡是看到双循环 + has() 求集合关系的代码,全部可以换成这两个方法。


2.3 RegExp.escape:用户字符串拼进正则之前,先过这一关

痛点(经典安全坑)

假设你要做一个「按关键词高亮」的功能,关键词来自用户输入:

function highlight(text, keyword) {
  // 灾难:用户输入 "a.b" 会变成正则 /a.b/,. 匹配任意字符
  // 用户输入 "(*)" 直接抛 SyntaxError
  return text.replace(new RegExp(keyword, 'g'), '**$&**');
}

用户输入 . + * ( ) [ 等正则元字符时,要么语义错乱,要么直接崩溃,更阴险的是 ReDoS(正则拒绝服务) 的入口。

旧解法与它的缺陷

以前要自己写 escape:

function escapeRegExp(s) {
  return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}

问题是:这个黑名单一旦漏掉一个元字符就出 bug,而且不同引擎元字符集合还可能变。你自己维护一份,迟早翻车。

新写法:RegExp.escape(ES2025,浏览器与 Node 均已落地)

const userInput = 'hello.world+(*)';
const safe = RegExp.escape(userInput); // "hello\.world\+\(\*\)"
new RegExp(safe, 'gi').test('hello.world+(*)'); // true,且语义严格等于字面量

RegExp.escape 由引擎保证「转义所有需要转义的字符」,你再也不用维护黑名单。凡是「用户字符串 + 正则」的组合,一律先 RegExp.escape


2.4 显式资源管理(Explicit Resource Management):JS 终于有了正经的 RAII

这是我个人认为 2024–2026 这批特性里工程价值最高的一个。

痛点:try/finally 地狱

资源(文件句柄、数据库连接、锁、定时器、订阅)用完了必须释放,传统写法:

const conn = await db.connect();
try {
  const lock = await lock.acquire();
  try {
    const file = await fs.open('data.txt');
    try {
      // ... 业务逻辑
    } finally {
      await file.close();
    }
  } finally {
    await lock.release();
  }
} finally {
  await conn.close();
}

嵌套越深越崩溃,而且只要中间某行提前 return 或抛异常,释放逻辑就必须靠 finally 兜住。人不是机器,漏写 finally 是常态。

概念:using、disposable 协议、DisposableStack

ES2024 引入了 using 声明(类似 C# 的 using、Python 的 with、Go 的 defer 思路):

// 任何带 [Symbol.dispose] 方法的对象,都能被 using 自动释放
class TempFile {
  constructor(name) { this.name = name; console.log('open', name); }
  read() { return 'content'; }
  [Symbol.dispose]() { console.log('close', this.name); } // 离开作用域自动调用
}

{
  using f = new TempFile('a.txt');
  console.log(f.read());
} // ← 这里自动打印 "close a.txt",无论上面是 return 还是 throw

三个核心概念:

  1. Symbol.dispose:同步释放钩子。对象出了 using 作用域(更准确说是作用域结束)时自动调用。
  2. Symbol.asyncDispose + await using:异步释放钩子,用于 close() 是异步的资源。
  3. DisposableStack / AsyncDisposableStack:当你有一组资源、且数量运行时才确定时,用栈来集中管理。
function processBatch(jobs) {
  using stack = new DisposableStack();
  const conn = stack.use(new DbConnection());   // 注册,结束时自动释放
  const lock = stack.use(new Mutex());
  for (const job of jobs) {
    const handle = stack.use(openTemp(job));
    // 即便循环中途 return / throw,stack 里的资源也会逆序释放
  }
} // conn、lock、所有 handle 逆序 dispose

异步版本:

{
  await using conn = await db.connect(); // asyncDispose 会被 await
  const rows = await conn.query('SELECT 1');
} // 自动 await conn.[Symbol.asyncDispose]()

为什么这比 try/finally 强? 因为释放逻辑和资源声明写在同一个地方,编译器/运行时保证「声明即释放」,你不可能漏。而且 DisposableStack 让「运行时数量的一组资源」也能安全释放——这是 try/finally 很难优雅表达的。

这一点也是和 Rust 的 Drop、Go 的 defer 对齐的工程范式。区别是 JS 的 using 是作用域驱动的(不是语句驱动的),更符合 JS 的函数作用域直觉。


2.5 Float16Array:半精度浮点,为 ML 和图形省一半内存

为什么需要它(ES2025)

ML 推理、WebGPU、图形处理里,权重和激活值大量使用 FP16(半精度浮点)。过去 JS 只有 Float32Array / Float64Array,要和底层二进制(比如 .onnx.gguf 模型文件、GPU buffer)打交道时,得自己算字节偏移、自己舍入,又慢又容易错。

const buf = new ArrayBuffer(8);
const f16 = new Float16Array(buf);
f16[0] = 1.337;
console.log(f16[0]);        // 1.3369140625(半精度只能精确到这)
console.log(Math.f16round(1.337)); // 同样是 1.3369140625,把 64 位浮点舍入成半精度

// 直接按半精度读写二进制,省一半内存
const weights = new Float16Array(modelBytes.buffer);

真实价值:同样一份模型权重,FP16 比 FP32 少占一半内存,且现代 GPU/TPU 的半精度算力往往是单精度的 2–8 倍。JS 终于能原生表达它,做端侧推理、WebGPU 计算时不用再当「二等公民」。


2.6 import attributes 与 JSON 模块: import 也要声明「你是什么」

安全语义(ES2025)

以前 import x from './config.json' 在不同打包器里行为不一致,甚至有安全歧义(比如把非 JSON 当 JSON 解析、或绕过 CSP)。现在标准写法:

import config from './config.json' with { type: 'json' };
import data from './a.json' assert { type: 'json' }; // 旧语法 assert 已弃用,用 with

with { type: 'json' }import attributes,它告诉引擎「我断言这是 JSON 模块」。好处:

  • 静态可分析:打包器/引擎在编译期就知道这是个 JSON 导入,能做更好的 tree-shaking 和安全检查。
  • 避免歧义:不会再因为扩展名或内容 sniffing 产生不一致行为。

如果以后要导入 CSS、WASM 等,也是走 with { type: '...' } 这套机制,是统一的「导入断言」框架。


2.7 三个「小但高频」的 ES2024 特性

Object.groupBy / Map.groupBy —— 分组再也不用 reduce 手搭:

const logs = [
  { level: 'error', msg: 'db down' },
  { level: 'info',  msg: 'boot' },
  { level: 'error', msg: 'timeout' },
];

const byLevel = Object.groupBy(logs, l => l.level);
// { error: [...], info: [...] }
// 注意:返回的是「null 原型对象」,避免和 Object.prototype 上的键冲突

Array.fromAsync —— 异步迭代器一键转数组:

async function* gen() { yield 1; yield 2; yield 3; }
const arr = await Array.fromAsync(gen()); // [1, 2, 3]
// 还能传 mapFn:Array.fromAsync(gen(), x => x * 2)

Promise.withResolvers —— 不用再在 new Promise 里「偷渡」resolve/reject:

// 旧写法:得在 executor 里把 resolve/reject 泄露到外层作用域
let resolve, reject;
const p = new Promise((res, rej) => { resolve = res; reject = rej; });

// 新写法:干净利落
const { promise, resolve, reject } = Promise.withResolvers();
// 适合「事件触发一次后 settle」的场景,比如等待某个外部信号

三、架构分析:这些特性怎么组合成「正确的管道」

单独用每个特性都不难,真正的价值是把它们组合成一个「惰性、安全、资源可控」的数据处理管道

先看一个典型的「坏架构」——一个日志分析脚本:

// 反例:读全量文件 → 全量 split → 全量正则 → 全量分组 → 内存爆炸
const text = fs.readFileSync('huge.log', 'utf8');   // 整文件进内存
const lines = text.split('\n');                     // 全量数组
const filtered = lines
  .filter(l => l.includes(keyword))                 // 再一份全量数组
  .map(parseLine);                                  // 再一份
const grouped = groupBy(filtered, l => l.level);    // 再一份
// 如果文件 20GB,这里直接 OOM

用本节讲的特性和「流式 + 惰性 + 资源安全」思路重写,架构变成:

文件流(惰性读取)
  → 逐行迭代(Iterator Helpers: map/filter)
  → 安全过滤(RegExp.escape 处理 keyword)
  → 分组统计(Object.groupBy,但用迭代器增量累积)
  → 资源释放(using 保证文件句柄关闭)

关键设计原则:

  1. 能惰性就别物化:文件一行一行处理,不 readFileSync 全量。
  2. 能用迭代器链就别用数组链Iterator Helpers 避免中间数组。
  3. 资源用 using:文件、连接、锁的释放写在一次,不可能漏。
  4. 用户字符串进正则必 RegExp.escape:这是安全基线,不是优化。

下面第三节的代码实战,就是把这个架构落成一份可运行的脚本。


四、代码实战:一个「资源安全 + 惰性 + 安全过滤」的日志分析器

下面是一份可直接 node 运行的完整示例,演示多个特性协同工作。我们实现一个 CLI:读日志文件,按用户给的关键词(安全转义)过滤,按日志级别分组统计,并对比两份日志里「只在 A 出现的错误源」用 Set 差集算出。

// log-analyzer.mjs
import { readFileSync, writeFileSync } from 'node:fs';
import { createInterface } from 'node:readline';

// ---- 1) 一个 disposable 的文件读取器,配合 using 自动关闭 ----
class LogSource {
  constructor(path) {
    this.path = path;
    this.lines = [];
    console.log('[open]', path);
  }
  // 把文件按行变成惰性迭代器(Generator 本身就是 Iterator)
  *readLines() {
    const content = readFileSync(this.path, 'utf8');
    for (const line of content.split('\n')) {
      yield line;
    }
  }
  [Symbol.dispose]() {
    console.log('[close]', this.path); // using 作用域结束自动调用
  }
}

// ---- 2) 解析单行日志:假设格式 "[LEVEL] message" ----
function parseLine(line) {
  const m = line.match(/^\[(\w+)\]\s*(.*)$/);
  if (!m) return null;
  return { level: m[1].toLowerCase(), msg: m[2] };
}

// ---- 3) 主流程 ----
function analyze(logPath, keyword) {
  // using:作用域结束自动关闭文件,无论中途 return 还是抛错
  using source = new LogSource(logPath);

  // 惰性迭代器链:map 解析 → filter 按关键词(安全转义!)→ 不需要 toArray
  const safeKeyword = RegExp.escape(keyword);        // 安全基线
  const matcher = new RegExp(safeKeyword, 'i');

  const matched = source
    .readLines()
    .map(parseLine)                 // 惰性解析
    .filter(line => line !== null)  // 丢弃无法解析的行
    .filter(line => matcher.test(line.msg)); // 按关键词过滤

  // 用 Object.groupBy 分组(这里为了演示先 toArray,真实超大文件可增量累积)
  const all = matched.toArray();
  const byLevel = Object.groupBy(all, l => l.level);

  // 统计每个级别的数量
  const summary = Object.fromEntries(
    Object.entries(byLevel).map(([lv, arr]) => [lv, arr.length])
  );

  return { total: all.length, summary, byLevel };
}

// ---- 4) 用 Set 差集对比两份日志的错误源 ----
function diffErrorSources(logA, logB) {
  const srcA = new Set(logA.byLevel.error?.map(e => e.msg) ?? []);
  const srcB = new Set(logB.byLevel.error?.map(e => e.msg) ?? []);
  return {
    onlyInA: [...srcA.difference(srcB)],  // A 有 B 没有的错误
    onlyInB: [...srcB.difference(srcA)],
  };
}

// ---- 运行 ----
const a = analyze('app.2026-08-15.log', 'timeout');
const b = analyze('app.2026-08-16.log', 'timeout');

console.log('A 统计:', a.summary);
console.log('B 统计:', b.summary);
console.log('错误源差异:', diffErrorSources(a, b));

// 结束后你会看到两个 [close] 日志,证明 using 真的释放了资源

这份代码把本文 80% 的核心特性串起来了:using 资源安全、RegExp.escape 安全过滤、Iterator Helpers 惰性处理、Object.groupBy 分组、Set.difference 集合运算。每一处都是「正确的默认做法」,你几乎不可能在这里写出资源泄漏或正则注入。

小提示:Node 20+ 支持 using(需要 --experimental-vm-modules?不,Explicit Resource Management 在 Node 22+ 默认开启,旧版本可用 --harmony-explicit-resource-management 打开)。Iterator Helpers / Set 方法在 Node 22+ 与近期浏览器均已稳定。


五、性能优化:什么时候用,什么时候别用

特性不是银弹。下面几条是我在真实项目里踩过、也优化过的经验。

5.1 惰性迭代器 vs 数组:大集合用惰性,小集合别折腾

惰性迭代器的最大收益在数据量大 + 只取一部分的场景。但迭代器链路本身有「每层一次函数调用」的开销,对很小的集合(比如 < 100 个元素),直接数组方法链反而更快、可读性也够。

// 大数据 + 截断:用惰性,省内存
const topUsers = await fetchPages(api).filter(u => u.active).take(100).toArray();

// 小集合:直接数组,别为了「优雅」硬上迭代器
const names = ['a', 'b', 'c'].map(s => s.toUpperCase()); // 这就够了

5.2 避免「惰性链 + 多次拉动」

迭代器是一次性、惰性的。如果你对一个迭代器链调用两次 .toArray(),它会执行两遍——这对无限序列或昂贵数据源是灾难:

const pipeline = source.readLines().map(parseLine).filter(Boolean);
pipeline.toArray(); // 第一遍
pipeline.toArray(); // 第二遍!source 被重新读一遍

// 正确:拉动一次,缓存结果
const data = pipeline.toArray();
use(data); useAgain(data);

5.3 Set 集合运算的复杂度

内置集合运算基于哈希,对两个大小分别为 m、n 的集合:

  • union / intersection / difference 复杂度约 O(min(m, n))(取决于实现,引擎会选较小的一侧做查找)。
  • 比你手写的「双重循环 O(m*n)」快得多,尤其集合大时。

所以:大集合求交集/差集,千万别手搓双重循环。直接用内置方法,既是性能也是正确性收益。

5.4 Float16 的内存账

一个 70 亿参数的模型,FP32 权重约 28 GB,FP16 约 14 GB。在浏览器/端侧推理里,这直接决定了「能不能跑」。用 Float16Array 原生表达,省去手动字节操作,也避免 JS 数字(一律 double)带来的内存膨胀。

5.5 using 不是万能的:跨 await 边界要注意

using 的释放时机是「离开词法作用域」。如果你的资源需要在 await 之后、作用域结束前持续可用,那没问题;但如果你把 using 资源传给了另一个异步函数、且那个函数的作用域比声明处更晚结束,释放时机可能比你预期得早。规则很简单:using 资源只在声明它的那个块里用,别逃逸出去。


六、总结与展望:现代 JS 正在变成「默认安全」的语言

把 2024–2026 这几年的特性连起来看,一条清晰的主线浮现出来:JS 正在从「灵活但容易写错」向「灵活且默认正确」演进

  • 资源管理using / DisposableStack 把 RAII 带进语言,资源泄漏从「靠人记」变成「靠运行时保证」。
  • 集合与迭代:Iterator Helpers + Set 集合运算,让「流式处理」和「集合关系」成为语言一等公民,不用再手搓容易写错的实现。
  • 安全基线RegExp.escape、import attributes,把「用户字符串拼正则」「模块类型歧义」这类经典坑从根上堵住。
  • 数值与二进制Float16Array 让 JS 在 ML / 图形时代不再缺位。

接下来值得盯的特性(状态提醒)

  • Records & Tuples:这个「不可变数据结构」提案已被 TC39 撤回,目前不在路线图中。别在文章/代码里当它是已落地特性。
  • Pipeline Operator(|>Pattern MatchingDecorators(Stage 3,仍不稳定):都很香,但都还没进正式版,生产环境别赌。
  • Iterator Helpers 的进一步扩展Iterator.range / Iterator.from 已在 ES2026 扶正):流式数据处理会继续是重点方向。

落地建议(务实版)

  1. 先升级运行时:Node 22+ / 近期 LTS、现代浏览器基本覆盖本文 90% 特性。查兼容性用 caniuse 或 MDN。
  2. Babel / TS 用户:这些特性大多有可靠 polyfill/downlevel 插件,可以放心用;using 需要对应 TS 5.2+ 与 Babel 插件支持。
  3. 从最小阻力处入手:明天写代码遇到 try/finally 关资源 → 改成 using;遇到双循环求交集 → 改成 Set.intersection;遇到「用户字符串 + 正则」 → 加 RegExp.escape。三处改完,你这一周的代码质量就上了一个台阶。

语言在变好,但「用对」永远比「用新」重要。把这些特性当成默认工具,而不是炫技手段,你的代码会更短、更安全、更不容易在凌晨三点炸。


参考资料:ECMAScript® 2024 / 2025 / 2026 Language Specification(TC39)、MDN Web Docs 对应条目、Node.js / V8 发布说明。文中代码示例均基于已稳定落地的特性,可直接在 Node 22+ 或现代浏览器运行(个别需开启对应 flag 的旧版本已注明)。

推荐文章

File 和 Blob 的区别
2024-11-18 23:11:46 +0800 CST
html一个全屏背景视频
2024-11-18 00:48:20 +0800 CST
HTML + CSS 实现微信钱包界面
2024-11-18 14:59:25 +0800 CST
企业官网案例-芊诺网络科技官网
2024-11-18 11:30:20 +0800 CST
MCP 测试文章 18007
2026-08-13 06:21:58 +0800 CST
程序员茄子在线接单