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:所有元素类型相同、需要高性能批量操作时使用
- DataView:需要混合类型、精确控制字节序时使用
三、TypedArray 类型一览
JavaScript 提供了 11 种 TypedArray 类型:
| 类型 | 字节数 | 值范围 | 等价 C 类型 | 描述 |
|---|---|---|---|---|
Int8Array | 1 | -128 ~ 127 | int8_t | 8 位有符号整数 |
Uint8Array | 1 | 0 ~ 255 | uint8_t | 8 位无符号整数 |
Uint8ClampedArray | 1 | 0 ~ 255 | uint8_t | 8 位无符号整数(溢出截断) |
Int16Array | 2 | -32768 ~ 32767 | int16_t | 16 位有符号整数 |
Uint16Array | 2 | 0 ~ 65535 | uint16_t | 16 位无符号整数 |
Int32Array | 4 | -2³¹ ~ 2³¹-1 | int32_t | 32 位有符号整数 |
Uint32Array | 4 | 0 ~ 2³²-1 | uint32_t | 32 位无符号整数 |
Float32Array | 4 | ±1.2×10⁻³⁸ ~ ±3.4×10³⁸ | float | 32 位浮点数 |
Float64Array | 8 | ±5×10⁻³²⁴ ~ ±1.8×10³⁰⁸ | double | 64 位浮点数 |
BigInt64Array | 8 | -2⁶³ ~ 2⁶³-1 | int64_t | 64 位有符号 BigInt |
BigUint64Array | 8 | 0 ~ 2⁶⁴-1 | uint64_t | 64 位无符号 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 专为 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(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 特性对比
| 特性 | 普通 Array | TypedArray |
|---|---|---|
| 元素类型 | 任意混合 | 单一数值类型 |
| 长度 | 可动态变化 | 固定不变 |
| 内存布局 | 可能不连续 | 保证连续 |
| 底层存储 | 引擎优化决定 | 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; // 主线程也能看到
};
由于 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 有什么区别?
核心区别有三点:
- 类型固定:TypedArray 每个元素类型相同且固定(如
Int32Array只存 32 位整数),普通 Array 可存任意类型 - 长度固定:TypedArray 创建后长度不可变,不支持
push/pop/splice,普通 Array 可动态增减 - 底层存储:TypedArray 基于
ArrayBuffer,内存连续紧凑,普通 Array 由引擎自行管理
适用场景:处理大量同类型数值数据(如图像、音频、网络协议)时用 TypedArray,通用业务逻辑用普通 Array。
Q2:ArrayBuffer、TypedArray、DataView 三者是什么关系?
ArrayBuffer → 原始内存块(不能直接读写)
├── TypedArray → 以固定类型解读整个缓冲区(高性能,类型统一)
└── DataView → 以灵活方式读写任意位置(可混合类型,可控制字节序)
ArrayBuffer 是底层容器,TypedArray 和 DataView 是两种不同风格的"眼镜"——透过它们来读写同一块内存。多个视图可以指向同一个 ArrayBuffer,修改会互相可见。
Q3:什么是字节序?为什么 TypedArray 开发时要注意字节序?
字节序是多字节数值在内存中的存储顺序:
- 小端序(Little-Endian):低字节在前,x86/ARM 平台常用
- 大端序(Big-Endian):高字节在前,网络协议常用
TypedArray 使用平台原生字节序(通常是小端),而网络协议通常使用大端序。如果直接用 TypedArray 解析网络数据,可能会读到错误的值。解决方案:使用 DataView 并显式指定字节序。
Q4:Uint8Array 和 Uint8ClampedArray 有什么区别?
两者都是 8 位无符号整数(范围 0~255),区别在于溢出处理:
Uint8Array:取模运算。256 → 0,-1 → 255Uint8ClampedArray:截断到边界。256 → 255,-1 → 0
Uint8ClampedArray 专为 Canvas ImageData 设计,截断行为更符合像素值处理的直觉。
Q5:TypedArray 的 subarray 和 slice 有什么区别?
subarray(begin, end):返回原 ArrayBuffer 上的一个新视图,共享底层内存,修改会互相影响slice(begin, end):复制数据到一个新的 ArrayBuffer,互不影响
类比:subarray 像给同一本书加了第二个书签,slice 像复印了其中几页。
Q6:SharedArrayBuffer 是什么?使用它有什么限制?
SharedArrayBuffer 允许主线程和 Web Worker 真正共享同一块内存(而不是复制或转移)。
限制:
- 需要跨源隔离:页面必须设置
Cross-Origin-Opener-Policy: same-origin和Cross-Origin-Embedder-Policy: require-corp响应头 - 存在竞态条件风险:多线程同时读写需要使用
Atomics进行同步 - 仅在安全上下文(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。