先记住这个答案
slice(start, end) 会新分配一块内存,把原缓冲区里指定范围的字节复制过去。两个缓冲区完全独立,改一个不影响另一个,代价是多占一份内存、复制要花时间。transfer() 不复制,它创建一个新的 ArrayBuffer 接管原来的数据,原缓冲区变成「已分离」状态,byteLength 变成 0,基于它创建的视图也全部失效。所以数据要分给多个地方用、原数据还要保留时用 slice();数据处理完交给下一步、原来的不再需要时用 transfer()。
slice()复制字节,两份数据互不影响transfer()移交内存,原缓冲区长度变 0- 转移后基于原缓冲区的旧视图全部失效
transfer(n)能改长度,变长补 0,变短截断
同样三个字节,复制和移交
对两个内容相同的缓冲区分别调用 slice() 和 transfer(),看原缓冲区的状态。
const copiedSource = Uint8Array.from([1, 2, 3]).buffer
const copied = copiedSource.slice(0)
new Uint8Array(copied)[0] = 9 // 只改副本
console.log(new Uint8Array(copiedSource)[0], new Uint8Array(copied)[0])
const movedSource = Uint8Array.from([4, 5, 6]).buffer
const moved = movedSource.transfer() // 数据移交给 moved
console.log(movedSource.byteLength, moved.byteLength)
console.log(movedSource.detached)
console.log(new Uint8Array(moved).join(','))查看输出与解释
1 9
0 3
true
4,5,6第一行说明 slice() 出来的副本是独立的,改了副本,原缓冲区的第一个字节还是 1。后面三行是 transfer():原缓冲区长度变成 0,detached 为 true,三个字节都到了 moved 里。
可以把 slice() 想成复印一份文件,原件还在你手里;transfer() 是把原件直接交给别人,你手里只剩一个空文件夹。
转移之后旧视图会怎样
ArrayBuffer 本身不能直接读写,要通过 Uint8Array、DataView 这些视图访问。转移之后,所有基于原缓冲区创建的视图都会失效:length 变成 0,按索引读到 undefined,调用 fill 这类方法会抛 TypeError。
- 改变长度
transfer()可以传一个新长度。比原来长,多出来的部分补 0;比原来短,后面的字节被截掉。不管哪种,原缓冲区都会被分离。这在「先分配一块大内存,处理完只保留有用部分」的场景里很方便,比如buffer.transfer(usedLength)。 所以代码里转移之后,旧变量应该当作已经作废,只把新缓冲区往下传。常见的写法是直接覆盖:buffer = buffer.transfer()。
容易答错的地方
- 转移后继续用旧的类型化数组
- 旧视图不会报错提醒你,读出来只是
undefined,很容易被当成数据丢了。转移后要基于新缓冲区重新创建视图。 - 把
ArrayBuffer.slice当成共享视图 ArrayBuffer的slice()一定会复制。只想看同一块内存的某一段、不想复制,应该创建带偏移的视图:new Uint8Array(buffer, offset, length)。类型化数组的subarray()也是共享内存的,而它的slice()会复制。
面试官还会怎么问?
不支持 transfer() 的环境怎么转移?
transfer() 是 ES2024 加入的,老环境可以用 structuredClone(buffer, { transfer: [buffer] }),效果一样,原缓冲区也会被分离。不能用 slice() 冒充,因为 slice() 之后原缓冲区还能用,行为不一样。
怎样判断一个 ArrayBuffer 已经被分离了?
新环境直接读 buffer.detached。只看 byteLength === 0 不可靠,因为本来就可以创建长度为 0 的缓冲区。
把大文件的数据交给 Worker,应该复制还是转移?
主线程之后不再需要这份数据,就转移:worker.postMessage(buffer, [buffer]),省掉一次复制。两边都要保留数据才复制。只有多个线程需要同时读写同一块内存时,才考虑 SharedArrayBuffer。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。