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/year/pending/proposal-typedarray-concat.md.
  • 简体中文
  • TypedArray Concat S1

    中文标题:类型化数组连接

    提案概览
    提案速览

    该提案为 %TypedArray%ArrayBufferSharedArrayBuffer 引入静态 concat 方法,以解决这些缓冲区类型简洁连接的需求。%TypedArray%.concat 处理同类型 TypedArray 的面向元素的连接,而缓冲区级别的方法支持面向字节的连接,并提供调整大小、增长或不可变性的选项。

    Note

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

    TypedArray、ArrayBuffer 和 SharedArrayBuffer 的连接

    ECMAScript 提案:TypedArray、ArrayBuffer 和 SharedArrayBuffer 的连接

    本提案目前处于流程阶段 1

    问题

    连接 TypedArray 和 ArrayBuffer 是一种常见操作,目前需要繁琐的手动缓冲区分配和数据复制;语言原生的 concat 方法将简化这一常见模式。

    Web 应用(无论是浏览器端还是服务器端)通常需要在数据管道中连接两个或多个 TypedArray 或 ArrayBuffer 实例。不幸的是,用于连接的现有机制相当冗长。

    一个常见示例是 WritableStream 实例,它在将写入数据作为一个合并块传递之前,会收集写入数据直到达到定义的阈值。服务器端应用通常依赖 Node.js 的 Buffer.concat API,而浏览器端应用则依赖浏览器兼容的 Buffer polyfill 或 TypedArray.prototype.set

    let buffers = [];
    let size = 0;
    new WritableStream({
      write(chunk) {
        buffers.push(chunk);
        size += chunk.length;
        if (size >= 4096) {
          flushBuffer(concat(buffers, size));
          buffers = [];
          size = 0;
        }
      }
    });
    
    function concat(buffers, size) {
      const dest = new Uint8Array(size);
      let offset = 0;
      for (const buffer of buffers) {
        dest.set(buffer, offset);
        offset += buffer.length;
      }
      return dest;
    }

    虽然这些方法可行,但它们需要大量冗长的样板代码。

    提案

    本提案提供了三个互补的静态连接方法:

    1. %TypedArray%.concat(items [, length]) — 对相同类型的 TypedArray 进行面向元素的连接
    2. ArrayBuffer.concat(items [, options]) — 面向字节的连接,返回 ArrayBuffer
    3. SharedArrayBuffer.concat(items [, options]) — 面向字节的连接,返回 SharedArrayBuffer

    所有这些方法都允许实现确定执行分配和复制的最优方法和最佳时机,但不需要特定的优化。

    %TypedArray%.concat 仅接受与构造函数相同类型的 TypedArray(例如,对于 Uint8Array.concat 必须全部是 Uint8Array),但这些 TypedArray 可以由 ArrayBuffer 或 SharedArrayBuffer 支持。ArrayBuffer.concatSharedArrayBuffer.concat 接受 ArrayBuffer、SharedArrayBuffer、TypedArray 和 DataView 输入的任何组合——返回类型由调用的方法决定,而不是由输入类型决定。

    %TypedArray%.concat(items [, length])

    将多个同类型的 TypedArray 连接成一个新的 TypedArray。

    const enc = new TextEncoder();
    const u8_1 = enc.encode('Hello ');
    const u8_2 = enc.encode('World!');
    const u8_3 = Uint8Array.concat([u8_1, u8_2]);
    // u8_3 内容:Uint8Array [72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33]
    • items — TypedArray 实例的可迭代对象,所有实例类型与构造函数相同。
    • length(可选)— 一个非负整数,指定结果的元素长度。如果小于总长度,结果将被截断;如果大于总长度,则零填充;默认为所有输入长度的总和。

    所有条目必须是与构造函数相同类型的 TypedArray(例如,对于 Uint8Array.concat,所有条目必须是 Uint8Array)。如果任何条目类型不同,则抛出 TypeError。条目可以由 ArrayBuffer 或 SharedArrayBuffer 支持。

    如果任何条目是已分离的 TypedArray,则抛出 TypeError。如果总元素数超过 253 - 1,则抛出 RangeError

    // 截断为 5 个元素
    const truncated = Uint8Array.concat([u8_1, u8_2], 5);
    
    // 零填充至 20 个元素
    const padded = Uint8Array.concat([u8_1, u8_2], 20);
    
    // WritableStream 合并示例
    let buffers = [];
    let size = 0;
    new WritableStream({
      write(chunk) {
        buffers.push(chunk);
        size += chunk.length;
        if (size >= 4096) {
          flushBuffer(Uint8Array.concat(buffers, size));
          buffers = [];
          size = 0;
        }
      }
    });

    concat 方法在所有 TypedArray 构造函数上可用:

    // 整数类型
    Int8Array.concat([new Int8Array([-1, 127]), new Int8Array([0, -128])]);
    // → Int8Array [-1, 127, 0, -128]
    
    Uint8Array.concat([new Uint8Array([0, 255]), new Uint8Array([128])]);
    // → Uint8Array [0, 255, 128]
    
    Uint8ClampedArray.concat([new Uint8ClampedArray([0, 255]), new Uint8ClampedArray([128])]);
    // → Uint8ClampedArray [0, 255, 128]
    
    Int16Array.concat([new Int16Array([-1, 32767]), new Int16Array([0])]);
    // → Int16Array [-1, 32767, 0]
    
    Uint16Array.concat([new Uint16Array([0, 65535]), new Uint16Array([256])]);
    // → Uint16Array [0, 65535, 256]
    
    Int32Array.concat([new Int32Array([-1, 2147483647]), new Int32Array([0])]);
    // → Int32Array [-1, 2147483647, 0]
    
    Uint32Array.concat([new Uint32Array([0, 4294967295]), new Uint32Array([256])]);
    // → Uint32Array [0, 4294967295, 256]
    
    // BigInt 类型
    BigInt64Array.concat([new BigInt64Array([0n, -1n]), new BigInt64Array([9007199254740991n])]);
    // → BigInt64Array [0n, -1n, 9007199254740991n]
    
    BigUint64Array.concat([new BigUint64Array([0n, 1n]), new BigUint64Array([18446744073709551615n])]);
    // → BigUint64Array [0n, 1n, 18446744073709551615n]
    
    // 浮点类型
    Float16Array.concat([new Float16Array([1.5, -0]), new Float16Array([Infinity, NaN])]);
    // → Float16Array [1.5, -0, Infinity, NaN]
    
    Float32Array.concat([new Float32Array([1.5, -0]), new Float32Array([Infinity, NaN])]);
    // → Float32Array [1.5, -0, Infinity, NaN]
    
    Float64Array.concat([new Float64Array([1.5, -0]), new Float64Array([Infinity, NaN])]);
    // → Float64Array [1.5, -0, Infinity, NaN]

    ArrayBuffer.concat(items [, options])

    将多个 ArrayBuffer、SharedArrayBuffer、TypedArray 或 DataView 的字节内容连接成一个新的 ArrayBuffer。

    const ab1 = new ArrayBuffer(4);
    const ab2 = new ArrayBuffer(4);
    const ab3 = ArrayBuffer.concat([ab1, ab2]);
    // ab3.byteLength === 8
    • items — ArrayBuffer、SharedArrayBuffer、TypedArray 或 DataView 实例的可迭代对象。对于 TypedArray 和 DataView 输入,仅包含底层缓冲区的视图部分。
    • options(可选)— 包含以下属性的对象:
      • length — 一个非负整数,指定结果的字节长度。如果小于总输入字节数,结果将被截断;如果大于总输入字节数,则零填充;默认为所有输入字节长度的总和。
      • resizable — 布尔值。如果为 true,结果是可调整大小的 ArrayBuffer,其中 length 指定最大字节长度(maxByteLength)。实际 byteLength 是总输入字节数与 length 中的较小值。默认为 false
      • immutable — 布尔值。如果为 true,结果是不可变的 ArrayBuffer,其内容无法更改、调整大小或分离。默认为 false此选项依赖于 `Immutable ArrayBuffer {proposal](https://github.com/tc39/proposal-immutable-arraybuffer)。

    resizableimmutable 选项互斥。如果两者都为 true,则抛出 TypeError

    如果缓冲区已分离或 DataView 越界,则抛出 TypeError。如果总字节数超过 253 - 1,则抛出 RangeError

    // ArrayBuffer、TypedArray 和 DataView 输入的混合
    const ab = new ArrayBuffer(4);
    const u8 = new Uint8Array([1, 2, 3, 4]);
    const dv = new DataView(new ArrayBuffer(2));
    const result = ArrayBuffer.concat([ab, u8, dv]);
    // result.byteLength === 10
    
    // 截断为 6 字节
    const truncated = ArrayBuffer.concat([ab, u8, dv], { length: 6 });
    
    // 零填充至 16 字节
    const padded = ArrayBuffer.concat([ab, u8], { length: 16 });
    
    // 创建具有增长空间的可调整大小结果
    const resizable = ArrayBuffer.concat([ab, u8], { resizable: true, length: 32 });
    // resizable.byteLength === 8(实际数据)
    // resizable.maxByteLength === 32(可增长至 32)
    
    // 创建不可变结果(需要 Immutable ArrayBuffer 提案)
    const immutable = ArrayBuffer.concat([ab, u8], { immutable: true });
    // immutable.byteLength === 8
    // immutable.immutable === true

    SharedArrayBuffer.concat(items [, options])

    将多个 ArrayBuffer、SharedArrayBuffer、TypedArray 或 DataView 的字节内容连接成一个新的 SharedArrayBuffer。

    const sab1 = new SharedArrayBuffer(4);
    const sab2 = new SharedArrayBuffer(4);
    const sab3 = SharedArrayBuffer.concat([sab1, sab2]);
    // sab3.byteLength === 8
    • items — ArrayBuffer、SharedArrayBuffer、TypedArray 或 DataView 实例的可迭代对象。对于 TypedArray 和 DataView 输入,仅包含底层缓冲区的视图部分。
    • options(可选)— 包含以下属性的对象:
      • length — 一个非负整数,指定结果的字节长度。如果小于总输入字节数,结果将被截断;如果大于总输入字节数,则零填充;默认为所有输入字节长度的总和。
      • growable — 布尔值。如果为 true,结果是可增长的 SharedArrayBuffer,其中 length 指定最大字节长度(maxByteLength)。实际 byteLength 是总输入字节数与 length 中的较小值。默认为 false

    注意:immutable 选项不适用于 SharedArrayBuffer。

    如果缓冲区已分离或 DataView 越界,则抛出 TypeError。如果总字节数超过 253 - 1,则抛出 RangeError

    // SharedArrayBuffer、TypedArray 和 DataView 输入的混合
    const sab = new SharedArrayBuffer(4);
    const u8 = new Uint8Array([1, 2, 3, 4]);
    const dv = new DataView(new ArrayBuffer(2));
    const result = SharedArrayBuffer.concat([sab, u8, dv]);
    // result.byteLength === 10
    
    // 创建具有增长空间的可增长结果
    const growable = SharedArrayBuffer.concat([sab, u8], { growable: true, length: 32 });
    // growable.byteLength === 8(实际数据)
    // growable.maxByteLength === 32(可增长至 32)

    set 的差异

    根据语言规范中 TypedArray.prototype.set 的当前定义,用户代码需要提前分配目标 TypedArray,并计算和更新每个复制段的偏移量。当有多个输入 TypedArray 时,分配可能很昂贵,簿记也可能很繁琐。set 算法也被设计为将复制的 TypedArray 的每个元素逐个复制到目标,不允许实现选择替代的、更优化的复制策略。

    为什么三个方法?

    %TypedArray%.concat 在 TypedArray 级别操作——它是面向元素的,要求相同类型的输入,并返回 TypedArray。这是处理类型化数据时正确的抽象级别(例如,在流中连接 Uint8Array 块)。

    ArrayBuffer.concatSharedArrayBuffer.concat 在缓冲区级别操作——它们是面向字节的,接受异构输入(ArrayBuffer/SharedArrayBuffer、TypedArray、DataView),并返回适当的缓冲区类型。这是控制缓冲区属性(如可调整大小/可增长性和不可变性)的正确抽象级别,这些属性是缓冲区的关注点,而不是 TypedArray 的关注点。

    ArrayBuffer.concatSharedArrayBuffer.concat 是单独的方法,因为返回类型不同,可用选项也不同(immutable 仅适用于 ArrayBuffer,growable 仅适用于 SharedArrayBuffer)。这反映了语言中 ArrayBufferSharedArrayBuffer 构造函数之间的现有分离。