可转移对象

可转移对象是拥有其资源可以从一个上下文转移到另一个上下文的对象,从而确保资源在同一时间只有一个上下文可用。转移后,原始对象将不再可用;它不再指向被转移的资源,任何尝试读取或写入该对象的操作都会抛出异常。

可转移对象通常用于共享一次只能安全地暴露给单个 JavaScript 线程的资源。例如,ArrayBuffer 是一个可转移对象,它拥有一个内存块。当此类缓冲区在线程之间转移时,关联的内存资源会从原始缓冲区中分离,并附加到新线程中创建的缓冲区对象上。原始线程中的缓冲区对象将不再可用,因为它不再拥有内存资源。

在使用 structuredClone() 创建对象的深拷贝时,也可以使用转移。克隆操作完成后,转移的资源会被移动到克隆对象,而不是被复制。

对于 postMessage()structuredClone(),被转移的资源必须附加到数据对象上,否则它们在接收端将不可用,因为可转移数组仅指示如何发送某些资源,但实际上并未发送它们(尽管它们总是会被分离)。

用于转移对象资源(或其底层数据)的机制取决于具体对象。例如,当 ArrayBuffer 在线程之间转移时,它指向的内存资源在上下文之间被字面地移动,这是一个快速高效的零拷贝操作。其他对象可能通过复制关联的资源然后从旧上下文中删除它来进行转移。

并非所有对象都是可转移的。下面提供了可转移对象列表

在线程之间转移对象

下面的代码演示了当从主线程向 Web Worker 线程发送消息时,转移是如何工作的。Uint8Array 在 Worker 中被复制(复制),而它的缓冲区被转移。转移后,主线程中对 uInt8Array 的任何读写尝试都将抛出异常,但您仍然可以检查 byteLength 以确认它现在为零。

js
// Create an 8MB "file" and fill it. 8MB = 1024 * 1024 * 8 B
const uInt8Array = new Uint8Array(1024 * 1024 * 8).map((v, i) => i);
console.log(uInt8Array.byteLength); // 8388608

// Transfer the underlying buffer to a worker
worker.postMessage(uInt8Array, [uInt8Array.buffer]);
console.log(uInt8Array.byteLength); // 0

注意:类型化数组,如 Int32ArrayUint8Array,是可序列化的,但不是可转移的。然而,它们底层的缓冲区是一个 ArrayBuffer,这是一个可转移的对象。我们可以将 uInt8Array.buffer 发送到 data 参数中,但不能将 uInt8Array 发送到 transfer 数组中。

在克隆操作期间进行转移

下面的代码展示了一个 structuredClone() 操作,其中底层缓冲区从原始对象复制到克隆对象。

js
const original = new Uint8Array(1024);
const clone = structuredClone(original);
console.log(original.byteLength); // 1024
console.log(clone.byteLength); // 1024

original[0] = 1;
console.log(clone[0]); // 0

// Transferring the Uint8Array would throw an exception as it is not a transferable object
// const transferred = structuredClone(original, {transfer: [original]});

// We can transfer Uint8Array.buffer.
const transferred = structuredClone(original, { transfer: [original.buffer] });
console.log(transferred.byteLength); // 1024
console.log(transferred[0]); // 1

// After transferring Uint8Array.buffer cannot be used.
console.log(original.byteLength); // 0

支持的对象

可以被转移的接口应在其介绍中包含此信息。

以下列出了一些不同规范中指示可以转移的项目(此列表可能不完整!)

注意:可转移对象在 Web IDL 文件中使用 [Transferable] 属性进行标记。浏览器支持可能在其各自对象的兼容性信息中通过 transferable 子功能(例如,请参阅 RTCDataChannel)来指示。

另见