跳到主要内容

Typed Array(类型化数组)

一、为什么需要 Typed Array?

JavaScript 的普通数组(Array)非常灵活——可以存储任意类型、动态扩容、自带丰富的方法。但这种灵活性是有代价的:

  • 内存不连续:普通数组底层可能使用哈希表实现,内存布局不紧凑
  • 类型不固定:每个元素都要存储类型信息,占用额外空间
  • 性能有限:无法直接与底层二进制数据交互

当我们需要处理大量同类型数值数据时(如图像像素、音频采样、网络协议包、3D 顶点坐标),普通数组就力不从心了。

Typed Array(类型化数组) 正是为此而生——它提供了一种高效的方式来读写原始二进制数据

二、核心架构:三层体系

Typed Array 的设计采用了缓冲区 + 视图的分层架构:

2.1 ArrayBuffer —— 原始内存

ArrayBuffer 是一块固定长度的连续内存区域,你可以把它想象成一排格子:

// 分配 16 字节的内存
const buffer = new ArrayBuffer(16);

console.log(buffer.byteLength); // 16

// ❌ 不能直接读写
// buffer[0] = 42; // 这样不行!

关键特性:

特性说明
固定长度创建后大小不可改变
初始化为 0所有字节初始值为 0
不可直接操作必须通过视图来读写
可转移所有权支持 transfer()Structured Clone

2.2 TypedArray —— 类型化视图

TypedArray 是一组构造函数的统称(并不存在名为 TypedArray 的全局对象),它们以特定的数值类型来解读 ArrayBuffer 中的字节:

const buffer = new ArrayBuffer(16);

// 以 32 位整数的视角来看这 16 字节 → 4 个元素
const int32View = new Int32Array(buffer);
console.log(int32View.length); // 4

// 以 8 位无符号整数的视角来看 → 16 个元素
const uint8View = new Uint8Array(buffer);
console.log(uint8View.length); // 16

// 它们共享同一块内存!
int32View[0] = 0x01020304;
console.log(uint8View[0]); // 4(小端序下最低字节在前)
console.log(uint8View[1]); // 3
console.log(uint8View[2]); // 2
console.log(uint8View[3]); // 1

2.3 DataView —— 灵活视图

当你需要在同一个缓冲区中混合读写不同类型的数据时(比如解析一个二进制协议),DataView 更加合适:

const buffer = new ArrayBuffer(12);
const view = new DataView(buffer);

// 在偏移 0 处写入一个 32 位整数
view.setInt32(0, 42);

// 在偏移 4 处写入一个 32 位浮点数
view.setFloat32(4, 3.14);

// 在偏移 8 处写入一个 16 位整数
view.setInt16(8, 256);

// 读取
console.log(view.getInt32(0)); // 42
console.log(view.getFloat32(4)); // 3.140000104904175(浮点精度)
console.log(view.getInt16(8)); // 256
TypedArray vs DataView 的选择
  • TypedArray:所有元素类型相同、需要高性能批量操作时使用
  • DataView:需要混合类型、精确控制字节序时使用

三、TypedArray 类型一览

JavaScript 提供了 11 种 TypedArray 类型:

类型字节数值范围等价 C 类型描述
Int8Array1-128 ~ 127int8_t8 位有符号整数
Uint8Array10 ~ 255uint8_t8 位无符号整数
Uint8ClampedArray10 ~ 255uint8_t8 位无符号整数(溢出截断)
Int16Array2-32768 ~ 32767int16_t16 位有符号整数
Uint16Array20 ~ 65535uint16_t16 位无符号整数
Int32Array4-2³¹ ~ 2³¹-1int32_t32 位有符号整数
Uint32Array40 ~ 2³²-1uint32_t32 位无符号整数
Float32Array4±1.2×10⁻³⁸ ~ ±3.4×10³⁸float32 位浮点数
Float64Array8±5×10⁻³²⁴ ~ ±1.8×10³⁰⁸double64 位浮点数
BigInt64Array8-2⁶³ ~ 2⁶³-1int64_t64 位有符号 BigInt
BigUint64Array80 ~ 2⁶⁴-1uint64_t64 位无符号 BigInt

Uint8Array vs Uint8ClampedArray

这两个类型都是 8 位无符号整数,但处理溢出的方式不同:

// Uint8Array:取模(mod 256)
const a = new Uint8Array(1);
a[0] = 256; // 256 % 256 = 0
console.log(a[0]); // 0
a[0] = -1; // 等价于 255
console.log(a[0]); // 255

// Uint8ClampedArray:截断到 [0, 255]
const b = new Uint8ClampedArray(1);
b[0] = 256; // 截断为 255
console.log(b[0]); // 255
b[0] = -1; // 截断为 0
console.log(b[0]); // 0
为什么有 Uint8ClampedArray?

Uint8ClampedArray 专为 Canvas ImageData 设计。图像像素值范围是 0~255,截断行为比取模更符合直觉——超出范围时"钉"在边界值上,而不是环绕。

四、创建 TypedArray 的方式

TypedArray 提供了多种创建方式,适应不同的使用场景:

4.1 直接指定长度

// 创建包含 8 个元素的 Float32Array,自动分配底层 ArrayBuffer
const arr = new Float32Array(8);
console.log(arr.length); // 8
console.log(arr.byteLength); // 32(8 × 4 字节)
console.log(arr.buffer); // ArrayBuffer(32)

4.2 从 ArrayBuffer 创建

const buffer = new ArrayBuffer(16);

// 完整映射
const full = new Int32Array(buffer); // 4 个元素

// 指定偏移量(必须是元素字节数的整数倍)
const partial = new Int32Array(buffer, 4); // 从偏移 4 开始,3 个元素

// 指定偏移量和长度
const slice = new Int32Array(buffer, 4, 2); // 从偏移 4 开始,取 2 个元素

4.3 从数组或可迭代对象创建

// 从普通数组
const a = new Uint8Array([10, 20, 30, 40]);

// 从另一个 TypedArray(会复制数据,不共享内存)
const b = new Float32Array(a);

// 使用 from 静态方法(支持映射函数)
const c = Int16Array.from([1, 2, 3], x => x * 10);
console.log(c); // Int16Array [10, 20, 30]

// 使用 of 静态方法
const d = Float64Array.of(1.1, 2.2, 3.3);

4.4 创建方式对比

五、常用操作

5.1 基本读写

const arr = new Int32Array(4);

// 索引读写(和普通数组一样)
arr[0] = 42;
arr[1] = 100;
console.log(arr[0]); // 42

// 越界赋值静默忽略(不会报错,也不会扩容)
arr[10] = 999;
console.log(arr[10]); // undefined
console.log(arr.length); // 4(长度不变)

5.2 遍历与迭代

TypedArray 支持大部分数组迭代方法:

const arr = new Float32Array([1.5, 2.5, 3.5, 4.5]);

// for...of
for (const val of arr) {
console.log(val);
}

// forEach
arr.forEach((val, idx) => console.log(`[${idx}] = ${val}`));

// map(返回同类型的 TypedArray)
const doubled = arr.map(x => x * 2);
console.log(doubled); // Float32Array [3, 5, 7, 9]

// filter(返回同类型的 TypedArray)
const big = arr.filter(x => x > 2);
console.log(big); // Float32Array [2.5, 3.5, 4.5]

// reduce
const sum = arr.reduce((acc, val) => acc + val, 0);
console.log(sum); // 12

// find / findIndex / every / some / includes 均可使用

5.3 复制与填充

const src = new Uint8Array([1, 2, 3, 4, 5]);
const dst = new Uint8Array(5);

// set:将数据复制到目标数组
dst.set(src); // 完全复制
dst.set([10, 20], 2); // 从索引 2 开始写入
console.log(dst); // Uint8Array [1, 2, 10, 20, 5]

// subarray:创建一个新视图(共享同一块内存!)
const sub = src.subarray(1, 3);
console.log(sub); // Uint8Array [2, 3]
sub[0] = 99;
console.log(src[1]); // 99(内存是共享的)

// slice:创建一个副本(不共享内存)
const copy = src.slice(1, 3);
copy[0] = 0;
console.log(src[1]); // 99(不受影响)

// fill:用指定值填充
const filled = new Uint8Array(5);
filled.fill(42);
console.log(filled); // Uint8Array [42, 42, 42, 42, 42]

filled.fill(0, 2, 4); // 从索引 2 到 4 填充 0
console.log(filled); // Uint8Array [42, 42, 0, 0, 42]
subarray vs slice
  • subarray(begin, end) → 返回共享内存的视图,修改会互相影响
  • slice(begin, end) → 返回独立的副本,修改互不影响

这一点与普通数组的 slice 行为一致,但 subarray 是 TypedArray 独有的。

5.4 排序

const arr = new Float32Array([3.1, 1.4, 4.1, 1.5, 9.2]);

// 默认按数值升序排序(不同于普通数组的字典序!)
arr.sort();
console.log(arr); // Float32Array [1.4, 1.5, 3.1, 4.1, 9.2]

// 自定义排序
arr.sort((a, b) => b - a); // 降序
console.log(arr); // Float32Array [9.2, 4.1, 3.1, 1.5, 1.4]
提示

普通数组的 sort() 默认将元素转为字符串后按 Unicode 排序(如 [10, 2].sort() 得到 [10, 2]),而 TypedArray 的 sort() 默认按数值升序排序,这通常更符合直觉。

六、字节序(Endianness)

字节序是指多字节数值在内存中的存储顺序,这在处理二进制数据时至关重要。

  • 小端序(Little-Endian):低字节在前。x86/x64 处理器、大部分 ARM 使用
  • 大端序(Big-Endian):高字节在前。网络协议(TCP/IP)使用

检测当前系统字节序

function getEndianness() {
const buffer = new ArrayBuffer(2);
new DataView(buffer).setInt16(0, 256, true); // true = 小端序写入
return new Int16Array(buffer)[0] === 256 ? '小端序' : '大端序';
}

console.log(getEndianness()); // 大多数情况输出 "小端序"

TypedArray 与 DataView 的字节序差异

const buffer = new ArrayBuffer(4);

// TypedArray:始终使用当前平台的字节序(通常小端序)
const int32 = new Int32Array(buffer);
int32[0] = 0x01020304;

const bytes = new Uint8Array(buffer);
console.log(bytes); // 小端序平台:Uint8Array [4, 3, 2, 1]

// DataView:可以显式指定字节序
const view = new DataView(buffer);
view.setInt32(0, 0x01020304, false); // false = 大端序
console.log(bytes); // Uint8Array [1, 2, 3, 4]

view.setInt32(0, 0x01020304, true); // true = 小端序
console.log(bytes); // Uint8Array [4, 3, 2, 1]
跨平台二进制数据交换

当你需要与网络协议、文件格式或其他平台交换二进制数据时,务必注意字节序。建议使用 DataView显式指定字节序,避免平台差异导致的 Bug。

七、实战应用场景

7.1 Canvas 图像处理

这是 Typed Array 最常见的应用之一。Canvas 的 ImageData 使用 Uint8ClampedArray 存储像素数据:

const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');

// 获取图像数据
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const pixels = imageData.data; // Uint8ClampedArray

// 每个像素占 4 个字节:R, G, B, A
for (let i = 0; i < pixels.length; i += 4) {
const r = pixels[i]; // 红色通道
const g = pixels[i + 1]; // 绿色通道
const b = pixels[i + 2]; // 蓝色通道
const a = pixels[i + 3]; // 透明度通道

// 转为灰度图
const gray = 0.299 * r + 0.587 * g + 0.114 * b;
pixels[i] = pixels[i + 1] = pixels[i + 2] = gray;
}

// 写回画布
ctx.putImageData(imageData, 0, 0);

7.2 WebSocket 二进制通信

const ws = new WebSocket('wss://example.com/binary');
ws.binaryType = 'arraybuffer'; // 接收 ArrayBuffer

ws.onmessage = (event) => {
const buffer = event.data;
const view = new DataView(buffer);

// 解析自定义二进制协议
const messageType = view.getUint8(0); // 第 1 字节:消息类型
const messageId = view.getUint32(1, true); // 第 2~5 字节:消息 ID(小端序)
const payload = new Uint8Array(buffer, 5); // 第 6 字节开始:负载数据

console.log(`类型: ${messageType}, ID: ${messageId}, 负载长度: ${payload.length}`);
};

// 发送二进制数据
function sendBinaryMessage(type, id, data) {
const buffer = new ArrayBuffer(5 + data.length);
const view = new DataView(buffer);

view.setUint8(0, type);
view.setUint32(1, id, true);
new Uint8Array(buffer, 5).set(data);

ws.send(buffer);
}

7.3 文件读取与处理

async function readFileAsTypedArray(file) {
const buffer = await file.arrayBuffer();

// 根据文件类型选择合适的视图
if (file.type === 'audio/wav') {
return parseWavFile(buffer);
}
}

function parseWavFile(buffer) {
const view = new DataView(buffer);

// WAV 文件头解析
const riff = String.fromCharCode(
view.getUint8(0), view.getUint8(1),
view.getUint8(2), view.getUint8(3)
);

if (riff !== 'RIFF') throw new Error('不是有效的 WAV 文件');

const channels = view.getUint16(22, true); // 声道数
const sampleRate = view.getUint32(24, true); // 采样率
const bitsPerSample = view.getUint16(34, true); // 位深度

console.log(`声道: ${channels}, 采样率: ${sampleRate}Hz, 位深: ${bitsPerSample}bit`);

// 获取音频数据(偏移 44 字节是 PCM 数据起始位置)
const audioData = new Int16Array(buffer, 44);
return { channels, sampleRate, bitsPerSample, audioData };
}

7.4 Web Worker 数据传输

// main.js
const worker = new Worker('worker.js');

// 创建大量数据
const data = new Float64Array(1_000_000);
for (let i = 0; i < data.length; i++) {
data[i] = Math.random();
}

// 方式一:复制传输(慢,数据被复制一份)
worker.postMessage(data);

// 方式二:转移所有权(快,零拷贝)
worker.postMessage(data.buffer, [data.buffer]);
// 注意:转移后 data 将不可用!
console.log(data.byteLength); // 0
// worker.js
self.onmessage = (event) => {
const buffer = event.data;
const arr = new Float64Array(buffer);

// 在 Worker 中处理数据
let sum = 0;
for (let i = 0; i < arr.length; i++) {
sum += arr[i];
}

self.postMessage({ average: sum / arr.length });
};

7.5 WebGL 顶点数据

// WebGL 需要 TypedArray 传递顶点数据
const vertices = new Float32Array([
// x, y, z
-0.5, -0.5, 0.0, // 左下
0.5, -0.5, 0.0, // 右下
0.0, 0.5, 0.0, // 顶部
]);

const gl = canvas.getContext('webgl');
const vertexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);

八、TypedArray 与普通 Array 的对比

8.1 特性对比

特性普通 ArrayTypedArray
元素类型任意混合单一数值类型
长度可动态变化固定不变
内存布局可能不连续保证连续
底层存储引擎优化决定ArrayBuffer
push/pop/splice✅ 支持❌ 不支持
map/filter/reduce✅ 支持✅ 支持
concat✅ 支持❌ 不支持
indexOf/includes✅ 支持✅ 支持
排序默认行为字典序数值序
性能(数值运算)一般更快
与二进制 API 兼容❌ 不兼容✅ 直接兼容

8.2 不支持的方法

TypedArray 不支持改变长度的方法:

const arr = new Int32Array([1, 2, 3]);

// ❌ 这些方法不存在
// arr.push(4);
// arr.pop();
// arr.splice(1, 1);
// arr.shift();
// arr.unshift(0);
// arr.concat(otherArr);

8.3 性能对比示例

const SIZE = 10_000_000;

// 普通数组
console.time('Array');
const normalArr = new Array(SIZE);
for (let i = 0; i < SIZE; i++) normalArr[i] = i * 1.5;
let sum1 = 0;
for (let i = 0; i < SIZE; i++) sum1 += normalArr[i];
console.timeEnd('Array');

// TypedArray
console.time('Float64Array');
const typedArr = new Float64Array(SIZE);
for (let i = 0; i < SIZE; i++) typedArr[i] = i * 1.5;
let sum2 = 0;
for (let i = 0; i < SIZE; i++) sum2 += typedArr[i];
console.timeEnd('Float64Array');

// 通常 TypedArray 在数值密集场景下更快
// 且占用更少的内存

九、SharedArrayBuffer 与 Atomics

9.1 SharedArrayBuffer

普通的 ArrayBuffer 在使用 postMessage 传递给 Worker 时,要么复制(慢)要么转移(原线程失去访问权)。SharedArrayBuffer 允许多个线程真正共享同一块内存

// main.js
const sab = new SharedArrayBuffer(1024);
const sharedArray = new Int32Array(sab);

const worker = new Worker('worker.js');
worker.postMessage(sab); // 不需要 transfer,内存是共享的

sharedArray[0] = 42;
// Worker 也能看到这个修改
// worker.js
self.onmessage = (event) => {
const sab = event.data;
const sharedArray = new Int32Array(sab);

console.log(sharedArray[0]); // 42(共享内存!)
sharedArray[1] = 100; // 主线程也能看到
};
SharedArrayBuffer 安全限制

由于 Spectre 漏洞,浏览器要求使用 SharedArrayBuffer 的页面必须处于跨源隔离环境中:

Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp

缺少这些响应头时,SharedArrayBuffer 将不可用。

9.2 Atomics —— 原子操作

多线程共享内存会带来竞态条件问题。Atomics 提供了原子操作来保证数据一致性:

const sab = new SharedArrayBuffer(4);
const shared = new Int32Array(sab);

// 原子写入
Atomics.store(shared, 0, 42);

// 原子读取
const value = Atomics.load(shared, 0);

// 原子加法(返回旧值)
const oldValue = Atomics.add(shared, 0, 10);
console.log(oldValue); // 42
console.log(Atomics.load(shared, 0)); // 52

// 原子比较并交换(CAS)
Atomics.compareExchange(shared, 0, 52, 100);
// 如果 shared[0] === 52,则将其设为 100

// 等待与通知(线程同步)
// 在 Worker 中等待
Atomics.wait(shared, 0, 100); // 当 shared[0] === 100 时阻塞

// 在主线程中通知
Atomics.notify(shared, 0, 1); // 唤醒 1 个等待者

Atomics 提供的方法一览:

方法说明
Atomics.load(arr, i)原子读取
Atomics.store(arr, i, val)原子写入
Atomics.add(arr, i, val)原子加法,返回旧值
Atomics.sub(arr, i, val)原子减法,返回旧值
Atomics.and(arr, i, val)原子按位与,返回旧值
Atomics.or(arr, i, val)原子按位或,返回旧值
Atomics.xor(arr, i, val)原子按位异或,返回旧值
Atomics.exchange(arr, i, val)原子交换,返回旧值
Atomics.compareExchange(arr, i, expected, val)CAS 操作
Atomics.wait(arr, i, val, timeout?)阻塞等待值变化
Atomics.notify(arr, i, count?)通知等待者

十、进阶技巧

10.1 多个视图共享同一块 Buffer

利用这个特性可以高效地进行不同粒度的数据操作:

const buffer = new ArrayBuffer(8);

const float64 = new Float64Array(buffer); // 1 个 64 位浮点数
const uint8 = new Uint8Array(buffer); // 8 个字节

// 通过字节视图观察浮点数的内存表示
float64[0] = Math.PI;
console.log('π 的二进制表示:');
console.log(uint8); // Uint8Array [24, 45, 68, 84, 251, 33, 9, 64]

10.2 高效合并多个 TypedArray

TypedArray 没有 concat 方法,需要手动合并:

function concatTypedArrays(...arrays) {
// 计算总长度
const totalLength = arrays.reduce((sum, arr) => sum + arr.length, 0);

// 创建目标数组(使用第一个数组的类型)
const result = new arrays[0].constructor(totalLength);

// 逐个拷贝
let offset = 0;
for (const arr of arrays) {
result.set(arr, offset);
offset += arr.length;
}

return result;
}

const a = new Uint8Array([1, 2, 3]);
const b = new Uint8Array([4, 5]);
const c = new Uint8Array([6, 7, 8, 9]);

const merged = concatTypedArrays(a, b, c);
console.log(merged); // Uint8Array [1, 2, 3, 4, 5, 6, 7, 8, 9]

10.3 Base64 编解码

// ArrayBuffer → Base64
function bufferToBase64(buffer) {
const bytes = new Uint8Array(buffer);
let binary = '';
for (let i = 0; i < bytes.length; i++) {
binary += String.fromCharCode(bytes[i]);
}
return btoa(binary);
}

// Base64 → ArrayBuffer
function base64ToBuffer(base64) {
const binary = atob(base64);
const bytes = new Uint8Array(binary.length);
for (let i = 0; i < binary.length; i++) {
bytes[i] = binary.charCodeAt(i);
}
return bytes.buffer;
}

10.4 TextEncoder / TextDecoder 与 TypedArray

// 字符串 → Uint8Array(UTF-8 编码)
const encoder = new TextEncoder();
const encoded = encoder.encode('你好,世界!');
console.log(encoded); // Uint8Array [228, 189, 160, 229, 165, 189, ...]

// Uint8Array → 字符串
const decoder = new TextDecoder('utf-8');
const decoded = decoder.decode(encoded);
console.log(decoded); // "你好,世界!"

十一、完整应用场景总结

十二、面试高频问答

Q1:TypedArray 和普通 Array 有什么区别?

核心区别有三点:

  1. 类型固定:TypedArray 每个元素类型相同且固定(如 Int32Array 只存 32 位整数),普通 Array 可存任意类型
  2. 长度固定:TypedArray 创建后长度不可变,不支持 push/pop/splice,普通 Array 可动态增减
  3. 底层存储:TypedArray 基于 ArrayBuffer,内存连续紧凑,普通 Array 由引擎自行管理

适用场景:处理大量同类型数值数据(如图像、音频、网络协议)时用 TypedArray,通用业务逻辑用普通 Array。


Q2:ArrayBuffer、TypedArray、DataView 三者是什么关系?

ArrayBuffer → 原始内存块(不能直接读写)
├── TypedArray → 以固定类型解读整个缓冲区(高性能,类型统一)
└── DataView → 以灵活方式读写任意位置(可混合类型,可控制字节序)

ArrayBuffer 是底层容器,TypedArrayDataView 是两种不同风格的"眼镜"——透过它们来读写同一块内存。多个视图可以指向同一个 ArrayBuffer,修改会互相可见。


Q3:什么是字节序?为什么 TypedArray 开发时要注意字节序?

字节序是多字节数值在内存中的存储顺序:

  • 小端序(Little-Endian):低字节在前,x86/ARM 平台常用
  • 大端序(Big-Endian):高字节在前,网络协议常用

TypedArray 使用平台原生字节序(通常是小端),而网络协议通常使用大端序。如果直接用 TypedArray 解析网络数据,可能会读到错误的值。解决方案:使用 DataView 并显式指定字节序。


Q4:Uint8Array 和 Uint8ClampedArray 有什么区别?

两者都是 8 位无符号整数(范围 0~255),区别在于溢出处理

  • Uint8Array:取模运算。256 → 0-1 → 255
  • Uint8ClampedArray:截断到边界。256 → 255-1 → 0

Uint8ClampedArray 专为 Canvas ImageData 设计,截断行为更符合像素值处理的直觉。


Q5:TypedArray 的 subarrayslice 有什么区别?

  • subarray(begin, end):返回原 ArrayBuffer 上的一个新视图,共享底层内存,修改会互相影响
  • slice(begin, end):复制数据到一个新的 ArrayBuffer,互不影响

类比:subarray 像给同一本书加了第二个书签,slice 像复印了其中几页。


Q6:SharedArrayBuffer 是什么?使用它有什么限制?

SharedArrayBuffer 允许主线程和 Web Worker 真正共享同一块内存(而不是复制或转移)。

限制:

  1. 需要跨源隔离:页面必须设置 Cross-Origin-Opener-Policy: same-originCross-Origin-Embedder-Policy: require-corp 响应头
  2. 存在竞态条件风险:多线程同时读写需要使用 Atomics 进行同步
  3. 仅在安全上下文(HTTPS)中可用

Q7:如何将 ArrayBuffer 与字符串、Base64 相互转换?

// 字符串 ↔ ArrayBuffer
const encoder = new TextEncoder();
const decoder = new TextDecoder();
const buf = encoder.encode('hello').buffer; // string → ArrayBuffer
const str = decoder.decode(buf); // ArrayBuffer → string

// ArrayBuffer → Base64
const base64 = btoa(String.fromCharCode(...new Uint8Array(buf)));

// Base64 → ArrayBuffer
const binary = atob(base64);
const arr = Uint8Array.from(binary, c => c.charCodeAt(0));

Q8:Web Worker 中使用 postMessage 传输 ArrayBuffer 时,"复制"和"转移"有什么区别?

  • 复制(默认):worker.postMessage(buffer),数据被完整拷贝一份,两端各持有独立副本,大数据时开销大
  • 转移(Transferable):worker.postMessage(buffer, [buffer]),所有权转移到 Worker,原线程的 buffer 变为零长度(不可用),零拷贝,性能极高

选择原则:如果发送方不再需要数据,优先用转移;如果双方都要访问,用 SharedArrayBuffer