类型化数组(Typed Arrays)提供了操作二进制数据的机制,用于处理音频、视频、图像等二进制数据。
类型化数组由两部分组成:
// 创建 ArrayBuffer(16 字节)
const buffer = new ArrayBuffer(16);
// 创建视图(32 位整数数组,4 个元素)
const view = new Int32Array(buffer);
console.log(view.length); // 4
ArrayBuffer 是固定长度的原始二进制数据缓冲区。
// 创建 16 字节的缓冲区
const buffer = new ArrayBuffer(16);
console.log(buffer.byteLength); // 16
// 不能直接操作 ArrayBuffer,需要通过视图
// buffer[0] = 1; // 错误
| 类型 | 字节 | 范围 | |------|------|------| | Int8Array | 1 | -128 到 127 | | Uint8Array | 1 | 0 到 255 | | Uint8ClampedArray | 1 | 0 到 255(截断) | | Int16Array | 2 | -32768 到 32767 | | Uint16Array | 2 | 0 到 65535 | | Int32Array | 4 | -2^31 到 2^31-1 | | Uint32Array | 4 | 0 到 2^32-1 | | Float32Array | 4 | 32 位浮点数 | | Float64Array | 8 | 64 位浮点数 |
const arr = new Int32Array(4); // 创建 4 个 32 位整数
console.log(arr.length); // 4
console.log(arr.byteLength); // 16 (4 * 4 字节)
const arr = new Int32Array([1, 2, 3, 4]);
console.log(arr); // Int32Array [1, 2, 3, 4]
const buffer = new ArrayBuffer(16);
const view = new Int32Array(buffer);
view[0] = 1;
view[1] = 2;
console.log(view); // Int32Array [1, 2, 0, 0]
const source = new Int32Array([1, 2, 3, 4]);
const copy = new Int32Array(source);
console.log(copy); // Int32Array [1, 2, 3, 4]
DataView 提供了更灵活的缓冲区访问方式,可以指定字节序。
const buffer = new ArrayBuffer(16);
const view = new DataView(buffer);
// 写入数据(大端序)
view.setInt32(0, 0x12345678, false); // false = 大端序
view.setInt32(4, 0x9ABCDEF0, false);
// 读取数据
console.log(view.getInt32(0, false).toString(16)); // '12345678'
console.log(view.getInt32(4, false).toString(16)); // '9abcdef0'
// 小端序
view.setInt32(0, 0x12345678, true); // true = 小端序
console.log(view.getInt32(0, true).toString(16)); // '78563412'
// 创建 RGBA 图像数据(100x100 像素)
const width = 100;
const height = 100;
const imageData = new Uint8ClampedArray(width * height * 4);
// 设置像素颜色(红色)
for (let i = 0; i < imageData.length; i += 4) {
imageData[i] = 255; // R
imageData[i + 1] = 0; // G
imageData[i + 2] = 0; // B
imageData[i + 3] = 255; // A
}
// 创建音频缓冲区(44100 Hz,1 秒,单声道)
const sampleRate = 44100;
const duration = 1; // 秒
const audioBuffer = new Float32Array(sampleRate * duration);
// 生成正弦波
const frequency = 440; // A4 音符
for (let i = 0; i < audioBuffer.length; i++) {
audioBuffer[i] = Math.sin(2 * Math.PI * frequency * i / sampleRate);
}
// 发送二进制数据
async function sendBinaryData(data) {
const buffer = new ArrayBuffer(data.length);
const view = new Uint8Array(buffer);
for (let i = 0; i < data.length; i++) {
view[i] = data.charCodeAt(i);
}
await fetch('/api/upload', {
method: 'POST',
body: buffer
});
}
async function readFileAsArrayBuffer(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(reader.result);
reader.onerror = reject;
reader.readAsArrayBuffer(file);
});
}
// 使用
const fileInput = document.querySelector('input[type="file"]');
fileInput.addEventListener('change', async (e) => {
const file = e.target.files[0];
const buffer = await readFileAsArrayBuffer(file);
const view = new Uint8Array(buffer);
console.log('文件大小:', view.length, '字节');
});
// 创建顶点位置数据
const vertices = new Float32Array([
-1.0, -1.0, 0.0, // 顶点 1
1.0, -1.0, 0.0, // 顶点 2
0.0, 1.0, 0.0 // 顶点 3
]);
// 创建 WebGL 缓冲区
const buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
// 字符串转 ArrayBuffer
function stringToArrayBuffer(str) {
const buffer = new ArrayBuffer(str.length);
const view = new Uint8Array(buffer);
for (let i = 0; i < str.length; i++) {
view[i] = str.charCodeAt(i);
}
return buffer;
}
// ArrayBuffer 转字符串
function arrayBufferToString(buffer) {
const view = new Uint8Array(buffer);
let str = '';
for (let i = 0; i < view.length; i++) {
str += String.fromCharCode(view[i]);
}
return str;
}
类型化数组支持大部分数组方法:
const arr = new Int32Array([1, 2, 3, 4, 5]);
// map
const doubled = arr.map(x => x * 2);
console.log(doubled); // Int32Array [2, 4, 6, 8, 10]
// filter
const evens = arr.filter(x => x % 2 === 0);
console.log(evens); // Int32Array [2, 4]
// reduce
const sum = arr.reduce((acc, x) => acc + x, 0);
console.log(sum); // 15
// forEach
arr.forEach((x, i) => {
console.log(`arr[${i}] = ${x}`);
});
// find
const found = arr.find(x => x > 3);
console.log(found); // 4
// slice
const sliced = arr.slice(1, 3);
console.log(sliced); // Int32Array [2, 3]
const arr = new Int32Array(4);
arr.length = 5; // 无效,长度固定
console.log(arr.length); // 4
// 类型化数组使用平台字节序
const buffer = new ArrayBuffer(4);
const view = new DataView(buffer);
view.setInt32(0, 0x12345678, true); // 小端序
// 读取时需要注意字节序
console.log(view.getInt32(0, true).toString(16)); // '12345678'
// Uint8Array 溢出会取模
const arr = new Uint8Array(1);
arr[0] = 300;
console.log(arr[0]); // 44 (300 % 256)
// Uint8ClampedArray 会截断到 0-255
const clamped = new Uint8ClampedArray(1);
clamped[0] = 300;
console.log(clamped[0]); // 255
// 类型化数组性能更好
const typed = new Float32Array(1000000);
const normal = new Array(1000000);
// 类型化数组操作更快
for (let i = 0; i < typed.length; i++) {
typed[i] = i;
}
const buffer = new ArrayBuffer(16);
const view1 = new Int32Array(buffer);
const view2 = new Int32Array(buffer);
view1[0] = 42;
console.log(view2[0]); // 42 (共享同一缓冲区)
const source = new Int32Array([1, 2, 3, 4]);
const target = new Int32Array(source.length);
target.set(source);
console.log(target); // Int32Array [1, 2, 3, 4]
const arr = new Int32Array([1, 2, 3, 4, 5]);
const sub = arr.subarray(1, 3);
console.log(sub); // Int32Array [2, 3]
// 修改子数组会影响原数组
sub[0] = 99;
console.log(arr); // Int32Array [1, 99, 3, 4, 5]
const typed = new Int32Array([1, 2, 3]);
const normal = Array.from(typed);
// 或
const normal2 = [...typed];
const arr = new Int32Array([1, 2, 3]);
console.log(arr instanceof Int32Array); // true
console.log(ArrayBuffer.isView(arr)); // true
// 图像数据使用 Uint8ClampedArray
const imageData = new Uint8ClampedArray(width * height * 4);
// 音频数据使用 Float32Array
const audioData = new Float32Array(sampleRate * duration);
// 整数计算使用 Int32Array
const integers = new Int32Array([1, 2, 3]);
// 创建可重用的缓冲区
const buffer = new ArrayBuffer(1024);
const view1 = new Int32Array(buffer, 0, 256); // 前 256 个整数
const view2 = new Int32Array(buffer, 1024, 256); // 后 256 个整数
// 需要控制字节序时使用 DataView
const view = new DataView(buffer);
view.setInt32(0, value, false); // 大端序