2025 年 TC39 都在忙什么?Import Bytes、Iterator Chunking 来了

TC39 2025:Import Bytes、Iterator Chunking 和那些即将落地的新特性

写跨平台的 JS 代码时,读个二进制文件都得写三套逻辑:

javascript 复制代码
// 浏览器
const bytes = await fetch('./photo.png').then(r => r.arrayBuffer());

// Node.js
const bytes = require('fs').readFileSync('./photo.png');

// Deno
const bytes = await Deno.readFile('./photo.png');

同样的需求,三种写法。想写个同构的图片处理库?先把这三套 API 适配一遍再说。

好消息是,TC39 在 2025 年推进了好几个提案来解决这类问题。这篇文章聊聊其中最值得关注的几个:Import Bytes、Iterator Chunking,以及今年已经进入 Stage 4 的新特性。

Import Bytes:一行代码搞定二进制导入

现在是什么状态

Stage 2.7(截至 2025 年 9 月),离正式标准就差临门一脚了。提案负责人是 Steven Salat,Guy Bedford 是共同作者。

核心语法

javascript 复制代码
import bytes from "./photo.png" with { type: "bytes" };
// bytes 是 Uint8Array,底层是不可变的 ArrayBuffer

动态导入也支持:

javascript 复制代码
const bytes = await import("./photo.png", { with: { type: "bytes" } });

就这么简单。不管你在浏览器、Node.js 还是 Deno,同一行代码,同样的结果。

为什么返回 Uint8Array 而不是 ArrayBuffer

提案选择返回 Uint8Array 而不是裸的 ArrayBuffer,理由挺实在的:

  1. 少一步操作 - 拿到 ArrayBuffer 你还得自己创建 TypedView,Uint8Array 直接就能用
  2. 跟现有 API 保持一致 - Response.bytes()Blob.bytes() 都返回 Uint8Array
  3. Node.js Buffer 兼容 - Buffer 本身就是 Uint8Array 的子类

为什么底层是不可变的 ArrayBuffer

这个设计决定挺有意思的。底层 ArrayBuffer 被设计成不可变的,原因有三:

  1. 避免共享可变状态 - 多个模块导入同一个文件,拿到的是同一个对象。如果可变,一个模块改了数据,其他模块全受影响
  2. 嵌入式场景 - 不可变数据可以直接放 ROM 里
  3. 安全性考虑 - 防止模块间通过共享 buffer 建立隐蔽通信通道

实际能干什么

图片处理

javascript 复制代码
import imageBytes from "./logo.png" with { type: "bytes" };
// 用 satori 之类的同构库处理
processImage(imageBytes);

加载字体

javascript 复制代码
import fontBytes from "./custom.woff" with { type: "bytes" };
// Canvas 或 PDF 生成时用
registerFont(fontBytes);

机器学习模型

javascript 复制代码
import modelBytes from "./model.bin" with { type: "bytes" };
loadModel(modelBytes);

工具链支持

好消息是,主流工具已经在跟进了。Deno 2.4、Bun 1.1.7 都有类似实现,Webpack、esbuild、Parcel 也支持类似的二进制导入机制。等提案正式落地,统一语法只是时间问题。

Iterator Chunking:迭代器分块终于有原生方案了

现在是什么状态

Stage 2.7(截至 2025 年 9 月),由 Michael Ficarra 主导。

两个核心方法

chunks(size) - 非重叠分块

javascript 复制代码
const numbers = [1, 2, 3, 4, 5, 6, 7].values();
const chunked = numbers.chunks(3);

for (const chunk of chunked) {
  console.log(chunk);
}
// [1, 2, 3]
// [4, 5, 6]
// [7]

windows(size) - 滑动窗口

javascript 复制代码
const numbers = [1, 2, 3, 4].values();
const windowed = numbers.windows(2);

for (const window of windowed) {
  console.log(window);
}
// [1, 2]
// [2, 3]
// [3, 4]

区别很直观:chunks 是切成一块一块互不重叠,windows 是滑动窗口每次移动一格。

解决什么问题

以前想做分块操作,要么自己写,要么引入 lodash:

javascript 复制代码
// lodash 方案
import chunk from 'lodash/chunk';
const chunks = chunk([1, 2, 3, 4], 2);

// 原生方案
const chunks = [1, 2, 3, 4].values().chunks(2);

原生方案的优势:

  • 不用装依赖
  • 惰性求值,内存友好
  • 跟整个迭代器生态无缝衔接
  • 支持异步迭代器

实际场景

批量 API 请求

javascript 复制代码
async function batchProcess(items) {
  const batches = items.values().chunks(50);

  for (const batch of batches) {
    await Promise.all(batch.map(item => api.process(item)));
    await sleep(1000); // 避免触发限流
  }
}

移动平均计算

javascript 复制代码
function movingAverage(numbers, windowSize) {
  return numbers
    .values()
    .windows(windowSize)
    .map(w => w.reduce((a, b) => a + b) / windowSize)
    .toArray();
}

const prices = [100, 102, 98, 105, 103, 107];
const ma3 = movingAverage(prices, 3);
// 3日移动平均

N-gram 生成

javascript 复制代码
function generateNGrams(text, n) {
  const words = text.split(' ');
  return words.values()
    .windows(n)
    .map(w => w.join(' '))
    .toArray();
}

const bigrams = generateNGrams("The quick brown fox", 2);
// ["The quick", "quick brown", "brown fox"]

边界情况的讨论

这个提案在推进过程中遇到了一个有意思的问题:如果迭代器元素少于窗口大小,windows() 应该返回什么?

javascript 复制代码
const small = [1, 2].values();
const result = small.windows(3); // 只有2个元素,请求3个的窗口

// 选项1:不返回任何窗口
// 选项2:返回 [1, 2] 作为不完整窗口

委员会讨论后认为两种场景都有合理的使用需求,所以决定把 windows() 拆分成多个方法来分别处理这两种情况。这也是提案从 Stage 2 到 Stage 2.7 花了点时间的原因。

2025 年进入 Stage 4 的特性

除了上面两个还在推进的提案,2025 年还有好几个特性已经正式"毕业"了:

RegExp.escape(2 月)

安全转义正则表达式字符串,防止注入:

javascript 复制代码
const userInput = "user@example.com (admin)";
const safePattern = RegExp.escape(userInput);
const regex = new RegExp(safePattern);
// 不用担心括号被解析成分组了

这个需求太常见了,以前都得自己写转义函数或者用第三方库。

Float16Array(2 月)

半精度浮点数的 TypedArray:

javascript 复制代码
const f16Array = new Float16Array([1.5, 2.7, 3.1]);

主要面向机器学习和图形处理场景。模型权重经常用 fp16 存储,有了原生支持就不用自己做转换了。

Error.isError(5 月)

可靠地判断一个值是不是 Error:

javascript 复制代码
if (Error.isError(value)) {
  console.log(value.message);
}

为什么不用 instanceof Error?因为跨 realm(比如 iframe 或 Node.js 的 vm 模块)的 Error 实例会被判成 false。这个方法解决了这个历史问题。

Math.sumPrecise(7 月)

高精度求和:

javascript 复制代码
const sum = Math.sumPrecise([0.1, 0.2, 0.3]);
// 比普通累加更精确,减少浮点误差累积

做金融计算或科学计算的应该会喜欢这个。

Uint8Array Base64 编解码(7 月)

原生的 Base64 编解码:

javascript 复制代码
const bytes = Uint8Array.fromBase64('SGVsbG8=');
const base64 = bytes.toBase64();
// 还有 fromHex() 和 toHex()

终于不用为了 Base64 转换去找第三方库了。

Explicit Resource Management(已 Stage 4)

using 关键字,自动资源清理:

javascript 复制代码
using file = await openFile('data.txt');
// 离开作用域自动关闭,不用手动 finally

借鉴了 Python 的 with 和 C# 的 using,解决了 JS 里资源管理一直很混乱的问题。

还有几个值得关注的 Stage 2 提案

Seeded PRNG(5 月进入 Stage 2)

可种子化的随机数生成器:

javascript 复制代码
const random = new Random(12345); // 种子
const value = random.next();
// 同样的种子,同样的序列

游戏开发、测试、仿真这些场景经常需要可重现的随机序列。

Error Stack Accessor(5 月进入 Stage 2)

标准化错误堆栈的访问方式。现在各个引擎的 error.stack 格式都不一样,这个提案要统一它。

提案流程简单回顾

TC39 的提案分 5 个阶段:

  • Stage 0:想法
  • Stage 1:正式提案,开始讨论
  • Stage 2:规范草案,API 基本稳定
  • Stage 2.7:规范文本接近完成,准备写测试
  • Stage 3:等待实现反馈
  • Stage 4:正式纳入标准

Import Bytes 和 Iterator Chunking 都到了 Stage 2.7,离 Stage 3 就差 test262 测试和浏览器实现承诺了。

总结

2025 年 TC39 的进展还是挺给力的:

  • Import Bytes 解决了跨平台二进制导入的老大难问题,同构库开发终于能省心了
  • Iterator Chunking 补上了迭代器工具链的空白,chunks 和 windows 覆盖了大部分分块场景
  • 一堆特性进入 Stage 4:RegExp.escape、Float16Array、Math.sumPrecise、Base64 编解码、资源管理...

这些特性有的已经可以通过 Babel 或 TypeScript 提前尝鲜了。如果你在用 Deno 或 Bun,Import Bytes 类似的功能现在就能用。


顺手安利几个我的开源项目:

Claude Code Skills (按需加载,意图自动识别,不浪费 token,介绍文章):

全栈项目(适合学习现代技术栈):

  • prompt-vault - Prompt 管理器,用的都是最新的技术栈,适合用来学习了解最新的前端全栈开发范式:Next.js 15 + React 19 + tRPC 11 + Supabase 全栈示例,clone 下来配个免费 Supabase 就能跑
  • chat_edit - 双模式 AI 应用(聊天+富文本编辑),Vue 3.5 + TypeScript + Vite 5 + Quill 2.0 + IndexedDB

参考链接

相关推荐
用户47949283569151 小时前
"讲讲原型链" —— 面试官最爱问的 JavaScript 基础
前端·javascript·面试
大怪v2 小时前
【Virtual World 04】我们的目标,无限宇宙!!
前端·javascript·代码规范
努力学算法的蒟蒻2 小时前
day27(12.7)——leetcode面试经典150
算法·leetcode·面试
狂炫冰美式3 小时前
不谈技术,搞点文化 🧀 —— 从复活一句明代残诗破局产品迭代
前端·人工智能·后端
xw53 小时前
npm几个实用命令
前端·npm
!win !3 小时前
npm几个实用命令
前端·npm
代码狂想家4 小时前
使用openEuler从零构建用户管理系统Web应用平台
前端
dorisrv5 小时前
优雅的React表单状态管理
前端
蓝瑟5 小时前
告别重复造轮子!业务组件多场景复用实战指南
前端·javascript·设计模式