For AI agents: the complete documentation index is available at /tc39-atlas/llms.txt, the full documentation bundle is available at /tc39-atlas/llms-full.txt, and this page is available as Markdown at /tc39-atlas/proposals/stage/4/arraybuffer-transfer.md.
  • 简体中文
  • ArrayBuffer transfer S4

    中文标题:ArrayBuffer 转移

    提案概览
    提案速览

    该提案向 ArrayBuffer.prototype 添加了 transfertransferToFixedLengthdetached getter,使得 ArrayBuffer 的程序化分离和所有权转移成为可能。该 API 支持所有权语义、类似 realloc 的大小调整、将可调整大小的缓冲区固定为固定长度以及权威的分离状态检查等使用场景。

    Note

    以下 README 来自上游仓库,其中的阶段或状态标注可能滞后;当前信息以提案概览为准。

    ArrayBuffer.prototype.transfer 及其相关方法

    阶段:4(已包含在 ES2024 中)。此仓库不再活跃。

    作者:Shu-yu Guo (@syg)

    拥护者:Shu-yu Guo (@syg)、Jordan Harband (@ljharb)、Yagiz Nizipli (@anonrig)

    简介

    ArrayBuffer 可能被 HTML 的序列化算法转移和分离,但缺少同样表达力的编程式 JS API。编程式 API 对于诸如转移 ArrayBuffer 所有权、优化重新分配(即 realloc 语义)以及将可调整大小的 ArrayBuffer 固定为固定长度等编程模式非常有用。本提案通过向 ArrayBuffer.prototype 添加新方法填补了这一表达力空白。

    本提案源自 可调整大小的缓冲区提案。在分离时,可调整大小的缓冲区处于阶段 3,而本提案被降级为阶段 2。

    API

    class ArrayBuffer {
      // ... 现有内容
    
      // 返回一个新的 ArrayBuffer,其字节内容与此缓冲区在 [0, min(this.byteLength, newByteLength)] 范围内相同,
      // 然后分离此缓冲区。
      //
      // 此缓冲区的最大字节长度及其可调整大小性在新 ArrayBuffer 中得到保留。
      //
      // 任何新内存都为零初始化。
      //
      // 如果 newByteLength 为 undefined,则将其设置为 this.byteLength。
      //
      // 设计为可作为无复制移动或 realloc 实现。
      //
      // 除非满足以下所有条件,否则抛出 RangeError:
      // - 0 <= newByteLength
      // - 如果此缓冲区可调整大小,则 newByteLength <= this.maxByteLength
      transfer(newByteLength);
    
      // 类似于 transfer,但始终返回不可调整大小的 ArrayBuffer。
      transferToFixedLength(newByteLength);
    
      // 返回此 ArrayBuffer 是否已分离。
      get detached();
    }

    动机和使用案例

    所有权

    “移动并分离原始 ArrayBuffer”方法可用于在使用 ArrayBuffer 时实现所有权语义。这在许多情况下都很有用,例如在写入缓冲区时禁止其他用户修改它。

    例如,考虑来自原始转移提案的 @domenic 的以下示例:

    function validateAndWrite(arrayBuffer) {
      // 进行一些异步验证。
      await validate(arrayBuffer);
    
      // 假设我们到达这里,内容有效;将其写入磁盘。
      await fs.writeFile("data.bin", arrayBuffer);
    }
    
    const data = new Uint8Array([0x01, 0x02, 0x03]);
    validateAndWrite(data.buffer);
    setTimeout(() => {
      data[0] = data[1] = data[2] = 0x00;
    }, 50);

    根据 await validate(arrayBuffer) 所花费的时间,验证结果可能因传递给 setTimeout 的回调而过时。防御性方法会先复制输入,但这明显性能较差:

    function validateAndWriteSafeButSlow(arrayBuffer) {
      // 先复制!
      const copy = arrayBuffer.slice();
    
      await validate(copy);
      await fs.writeFile("data.bin", copy);
    }

    使用 transfer,所有权转移可以简洁地表达:

    function validateAndWriteSafeAndFast(arrayBuffer) {
      // 转移以取得所有权,实现可以选择将其实现为零拷贝移动。
      const owned = arrayBuffer.transfer();
    
      // 此后 arrayBuffer 已分离。
      assert(arrayBuffer.detached);
    
      await validate(owned);
      await fs.writeFile("data.bin", owned);
    }

    重新分配

    相同的 transfer API,在传递 newByteLength 参数时,可以兼作具有与 realloc 相同的表达力。操作系统通常比复制更高效地实现 realloc

    将可调整大小的缓冲区固定为固定长度

    transferToFixedLength 方法是 transfer 的一种变体,它总是返回固定长度的 ArrayBuffer。这在缓冲区不再需要可调整大小的情况下很有用,允许实现在可调整大小的缓冲区原地实现时释放虚拟内存(即提前保留地址空间)。

    检查分离状态

    由于 TypedArray 和 ArrayBuffer 标准化的混乱历史以及保持 Web 兼容性,对于已分离缓冲区上的 TypedArray 视图,某些操作会抛出异常(例如原型方法),而其他操作则返回哨兵值(0undefined)(例如索引访问和长度)。

    添加 detached getter 以权威地判断 ArrayBuffer 是否已分离。

    目前,没有高效的方法来检测 ArrayBuffer 是否已分离。以下是检测分离状态的一种实现示例,但在 V8 中存在一些缺陷:V8 不会内联带有 try-catch 块的函数。另请参见 此 Node 内部注释

    const assert = require('node:assert')
    
    function isBufferDetached(buffer) {
      if (buffer.byteLength === 0) {
        try {
          new Uint8Array(buffer);
        } catch (error) {
          assert(error.name === 'TypeError');
          return true;
        }
      }
      return false
    }

    常见问题解答与设计权衡

    为什么同时存在 transfertransferToFixedLength 而不是一个更灵活的方法?

    大多数人似乎直觉认为移动语义作为主要使用案例应保留可调整大小性。HTML 序列化中的 ArrayBuffer 转移保留了可调整大小性,与此对称有利于直觉。

    灵活的 transfer 也会使 API 设计对于少数使用案例变得复杂,因此采用了独立的 transferToFixedLength 方法。

    为什么不能向 transfer 传递新的 maxByteLength

    transfer 的目标之一,除了分离语义,是在效率上可优于用户代码中的复制。作者不清楚对于原地实现的可调整大小缓冲区,在所有流行的操作系统上重新分配虚拟内存页面是否可能且高效。

    此外,这增加了复杂性,服务于更少数使用案例。从一开始就应该以足够的最大大小分配可调整大小的缓冲区。

    如果性能是目标,为什么不添加新方法而是将写时复制(CoW)实现为透明优化?

    一句话:安全。

    ArrayBuffer 是攻击 JavaScript 引擎时非常流行的攻击向量。引擎采用的一项重要安全缓解措施是确保 ArrayBuffer 的数据指针是常量且不移动。出于同样原因,可调整大小的缓冲区被指定为允许原地实现。

    CoW ArrayBuffer 可以通过移动数据指针实现。当修改 CoW ArrayBuffer 时,会分配新内存并更新后备存储。然而,这与安全缓解措施相冲突。

    只有在底层操作系统的额外帮助下,才可能同时实现写时复制 ArrayBuffer 并保持“固定数据指针”安全缓解措施:通过映射标记为 CoW 的新虚拟内存并最初指向与源缓冲区相同的物理页面。然而,这种技术并非可移植。

    目前,Google Chrome 认为此缓解措施对安全足够重要,因此不实现 CoW ArrayBuffer

    get detached() 在浏览器和运行时中的使用情况如何?

    transfer 在浏览器和运行时中的使用情况如何?

    大多数浏览器使用 detach() 作为分离 ArrayBuffer 的名称。

    开放问题

    我们真的需要 transferToFixedLength 吗?

    完善表达力感觉不错,但承认这里的使用案例不如 transfer 那么引人注目。

    历史与致谢

    感谢: