移譲可能オブジェクト

移譲可能オブジェクト (Transferable objects) は、あるコンテキストから別のコンテキストへ 移譲 することができるリソースを自分自身で所有するオブジェクトで、そのリソースが一度に 1 つのコンテキストでのみ利用できることを保証するものです。 移譲が終わると、元のオブジェクトは使えなくなります。移譲されたリソースをもう指しておらず、オブジェクトの読み書きをしようとすると例外が発生します。

移譲可能なオブジェクトは一般的に、一度に単一のJavaScriptスレッドにしか安全に公開されないリソースを共有するために使用されます。 例えば、ArrayBuffer はメモリーブロックを自分自身で所有する移譲可能なオブジェクトです。 このようなバッファーがスレッド間で移譲されるとき、関連するメモリーリソースは元のバッファーから切り離され、新しいスレッドで作成されたバッファーオブジェクトに装着されます。 元のスレッドのバッファーオブジェクトは、もはやメモリリソースを所有していないため、使用できなくなります。

移譲は structuredClone() でオブジェクトのディープコピーを作成するときにも使用されるかもしれません。 複製操作の後に、移譲されたリソースは、コピーされたオブジェクトにコピーされるのではなく、移動されます。

オブジェクトのリソースを移譲するために使用されるメカニズムは、オブジェクトに依存します。 例えば、 ArrayBuffer がスレッド間で移譲されるとき、それが指すメモリーリソースは高速で効率の良いゼロコピー操作により、文字通りコンテキスト間で移動されます。 他にも、関連するリソースをコピーして、古いコンテキストから削除することで、オブジェクトを移譲することができる場合もあります。

すべてのオブジェクトが移譲可能なわけではありません。 移譲可能なオブジェクトの一覧は下記の通りです

オブジェクトのスレッド間の移譲

以下のコードは、メインスレッドからウェブワーカースレッドにメッセージを送信する際に、移譲がどのように動作するのかを示しています。 Uint8Array は、バッファーが移譲されている間、ワーカー内でコピーされます(複製されます)。 移譲後、メインスレッドから uInt8Array を読み書きしようとするとエラーが発生しますが、byteLength を調べるとゼロになったことが確認できます。

js
// 8MB の "file" を作成して中身を埋めます。 8MB = 1024 * 1024 * 8 B
const uInt8Array = new Uint8Array(1024 * 1024 * 8).map((v, i) => i);
console.log(uInt8Array.byteLength); // 8388608

// 下層のバッファーからワーカーに移譲する
worker.postMessage(uInt8Array, [uInt8Array.buffer]);
console.log(uInt8Array.byteLength); // 0

メモ: 型付き配列 Int32ArrayUint8Arrayシリアライズ可能ですが、移譲は行えません。 しかし、その下にあるバッファーは ArrayBuffer であり、これは移譲可能なオブジェクトです。 data 引数に uInt8Array.buffer を設定すれば、移譲する配列に uInt8Array がなくても、送ることができます。

複製操作中の移譲について

以下のコードは、 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

// Uint8Array を移譲すると、移譲可能なオブジェクトではないため、例外が発生します。
// const transferred = structuredClone(original, {transfer: [original]});

// Uint8Array.buffer ならば移譲できます。
const transferred = structuredClone(original, { transfer: [original.buffer] });
console.log(transferred.byteLength); // 1024
console.log(transferred[0]); // 1

// Uint8Array.buffer は移譲した後は使用することができません。
console.log(original.byteLength); // 0

対応しているオブジェクト

各種仕様書が示す移譲可能な項目は以下の通りです。

ブラウザーの対応は、それぞれのオブジェクトの互換性情報の transferable サブ機能で示す必要があります(例として RTCDataChannel を参照してください)。 この記事を書いている時点では、すべての移譲可能なオブジェクトでこの情報が更新されているわけではありません。

メモ: 移譲可能なオブジェクトは Web IDL ファイル の中で [Transferable] という属性でマークアップされています。

関連情報