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

参考链接

相关推荐
崔庆才丨静觅3 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60614 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了4 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅4 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅5 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅5 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment5 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅5 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊5 小时前
jwt介绍
前端
爱敲代码的小鱼6 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax