WorkerやIndexedDBへ値を渡すとき、JavaScriptは通常のオブジェクト参照をそのまま共有せず、structured cloneアルゴリズムで値を複製できます。一部のオブジェクトはtransferリストに指定し、複製ではなく移動できます。どちらもデータを送る操作に見えますが、受信側に別の複製ができるのか、送信側が元の値を使い続けられるかが異なります。

複製とバッファ所有権移動の概念図

実際のメモリ配置や性能測定を示す図ではありません。

1. Structured cloneが複製するもの

HTML Standardはstructured serializationとdeserializationを定義しています。structuredClone()はこのアルゴリズムで対応する値を複製します。WorkerのpostMessage()やIndexedDBなども、実行コンテキストを越えるデータの受け渡しや保存に利用します。受信側には値が再構成され、一般のオブジェクト参照を両側で同じように変更する方式とは異なります。

「複製」は任意のJavaScriptオブジェクトの動作をすべて維持する意味ではありません。関数オブジェクトは複製できず、DataCloneErrorになります。独自prototypeやdescriptorは保たれません。直列化時にgetterが動く場合がありますがアクセサー自体は復元されません。対応するグラフの循環や反復参照は複製内に保たれます。APIが対応する値でメッセージを作り、重要な動作はクラス内部に隠さず明示的なデータとして表現します。

2. Transferableは複製せずに移す

一部のオブジェクトはtransferできます。たとえば大きなArrayBufferをworker.postMessage({ buffer }, [buffer])でWorkerへ送ると、受信側に新しいバッファラッパーができ、送信側の基盤データは切り離され、既存ビューからも使えなくなります。ラッパーの全プロパティが例外になる意味ではありません。これは高速な複製の別表記ではなく、所有権を移す操作です。

transferは複数の実行文脈が同じメモリを同時に使う共有メモリとも異なります。SharedArrayBufferはtransferできません。対応する直列化でもデータは共有され、分離や保存の制約があります。transferは対応オブジェクトの所有権を移動する方法です。シリアライズ可能、transfer可能、共有可能という性質を分けて考えてください。

3. transferリストと寿命の設計

リストは転送方式を指定し、ペイロードに値を追加しません。このバッファ例では受信側がアクセスできるようペイロードからバッファへ到達できる必要があります。typed arrayのビューでなく基盤バッファを移します。重複や非対応の項目は失敗し得ます。structuredClone(value, { transfer: [buffer] })のように複製APIでも利用できます。転送後に送信側が元バッファを読み取る設計にしないでください。複数の処理段階で同じデータが必要なら、複製するのか、所有権を渡すのか、別の共有方式を使うのかを先に決めます。

小さなオブジェクトすべてをtransferしようとしたり、どのメッセージも複製コストが同じだと考えたりしないでください。structured cloneは対応値の複製に使い、transferは限られた型に使えます。最適化前にメッセージの大きさと複製コストを測り、リスト外の値も型と転送先に応じて確認します。すべてのtransferが物理的な複製を省くとは限りません。

4. 実務家の視点:境界で値と所有権を検証

Surmaの当時の解説は浅い複製と構造化複製を区別します。過去の性能論はここでの測定結果ではありません。Workerとのメッセージ設計では受け付ける値の形、複製できない値、transfer対象、転送後に送信側で利用できるデータを文書化します。受信側でも値と構造を検証してください。structured cloneはデータ受け渡しの仕組みであり、入力検証やセキュリティ方針の代わりではありません。複製に成功しても信頼性やアプリケーション要件への適合は証明されません。

受信側に期待したバッファが届くこと、転送後に送信側の元データが使えなくなること、関数など非対応値で失敗することを試します。両方の側から同じメモリへアクセスする必要がある場合は、共有メモリAPIと同期要件を別に検討します。動作の保存が必要なオブジェクトは明示的なデータに変換し、受信側で必要な処理を再構成する方が分かりやすくなります。

参考資料