JavaScript TypedArray 截取:只剩两项,为什么 buffer 仍然带着整段数据

前天 3阅读

解析一段二进制数据时,用 subarray 取出中间两字节,再把它的 buffer 交给下一层,结果下一层又看到头尾的额外字节。视图范围变小,不代表底层缓冲区也被裁短;只传 buffer 会丢掉窗口边界。

下面使用普通 Uint8Array、固定大小 ArrayBuffer 和内存数据。保存为 demo.mjs,用 node demo.mjs 运行;不涉及 Buffer 子类、共享内存或可调整大小的缓冲区。

JavaScript TypedArray 截取:只剩两项,为什么 buffer 仍然带着整段数据

AI模型生成概念示意:绿色窗口仍覆盖原缓冲的一段,橙色块表示独立复制出来的内容;非实测截图。

import assert from "node:assert/strict";

const source = new Uint8Array([10, 20, 30, 40]);
const view = source.subarray(1, 3);
const copy = source.slice(1, 3);
assert.equal(view.buffer, source.buffer);
assert.notEqual(copy.buffer, source.buffer);
assert.equal(view.byteOffset, 1);
assert.equal(view.byteLength, 2);
console.log("view offset/bytes:", view.byteOffset, view.byteLength);

view[0] = 99;
assert.deepEqual([...source], [10, 99, 30, 40]);
assert.deepEqual([...copy], [20, 30]);
console.log("source after view write:", [...source].join(","));
console.log("copy after view write:", [...copy].join(","));

source[2] = 77;
assert.deepEqual([...view], [99, 77]);
console.log("view after source write:", [...view].join(","));

const wholeBuffer = new Uint8Array(view.buffer);
const exactWindow = new Uint8Array(view.buffer, view.byteOffset, view.byteLength);
assert.deepEqual([...wholeBuffer], [10, 99, 77, 40]);
assert.deepEqual([...exactWindow], [99, 77]);
console.log("buffer alone:", [...wholeBuffer].join(","));
console.log("buffer with bounds:", [...exactWindow].join(","));

先确认谁和谁共享存储

六行输出依次为 view offset/bytes: 1 2、source after view write: 10,99,30,40、copy after view write: 20,30、view after source write: 99,77、buffer alone: 10,99,77,40、buffer with bounds: 99,77。断言还直接比较 buffer 身份,避免仅凭数值相同猜测是否复制。

subarray(1, 3) 使用左闭右开的元素范围,所以窗口包含原位置一、二。修改 view[0] 写到的是 source[1];随后改 source[2],窗口的第二项也立即变成七十七。两条方向都能观察到变化,因为读写落在同一片字节存储。

slice(1, 3) 为本例普通 Uint8Array 建立独立副本。它保留创建时的二十和三十,后续修改源或视图都不影响这两个值。要保存不可随原缓冲变化的快照,应在清楚范围后复制,而不只是换一个变量名。

交付窗口时同时交付边界

new Uint8Array(view.buffer) 从底层缓冲区开头建立视图,因此重新暴露四个字节。传入 view.byteOffset 与 view.byteLength 后,才还原所需的两字节窗口。若下一层原本接收 TypedArray,直接传 view 通常更容易保留这些信息。

这里每个元素恰好一字节,所以 byteLength 可以作为 Uint8Array 构造器的元素个数。换成 Uint16Array 等类型时,构造器第三个参数仍是元素数,byteOffset 则仍以字节计;还需满足对齐条件,不能机械复制这组参数。

一个很小的视图也可能让整块大缓冲区继续被引用。若窗口需要长期保存,复制少量有效数据可能更合适;若只是同步解析中的临时窗口,共享可以减少复制。应结合持有时间和内存规模选择,本文没有对性能作基准结论。

这些结果限定于示例的普通 Uint8Array。带自定义构造行为的子类,以及 Node.js Buffer 的同名方法,应按各自接口核对。测试二进制接口时,把缓冲身份、偏移、长度和修改后的值一起检查,才能发现范围正确但传递方式错误的问题。

资料核对日期:2026年10月2日。最终展示代码在 Node.js v24.19.0 独立运行并通过全部断言;结果对应文中固定输入。

官方参考

文章版权声明:除非注明,否则均为云鹊BLOG原创文章,转载或复制请以超链接形式并注明出处。