TypedArray 是 ES6 引入的用于处理二进制数据的数组类型,在 WebGL、Canvas、文件处理等场景中非常重要。
为什么需要 TypedArray#
普通数组可以存储任意类型,但处理二进制数据效率较低:
// 普通数组:每个元素可以是任意类型const arr = [1, 'hello', { x: 1 }, 3.14]
// TypedArray:每个元素类型固定,内存连续const uint8 = new Uint8Array([1, 2, 3, 4])// 每个元素占用 1 字节,值范围 0-255ArrayBuffer#
ArrayBuffer 是固定长度的二进制数据缓冲区:
// 创建 16 字节的缓冲区const buffer = new ArrayBuffer(16)console.log(buffer.byteLength) // 16
// ArrayBuffer 不能直接操作,需要通过视图// buffer[0] = 1; // 不起作用
// 检查是否是 ArrayBufferArrayBuffer.isView(buffer) // falseTypedArray 类型#
ES6 提供了 9 种 TypedArray 类型:
| 类型 | 字节 | 范围 | 描述 |
|---|---|---|---|
| Int8Array | 1 | -128 ~ 127 | 8位有符号整数 |
| Uint8Array | 1 | 0 ~ 255 | 8位无符号整数 |
| Uint8ClampedArray | 1 | 0 ~ 255 | 8位无符号整数(溢出钳位) |
| Int16Array | 2 | -32768 ~ 32767 | 16位有符号整数 |
| Uint16Array | 2 | 0 ~ 65535 | 16位无符号整数 |
| Int32Array | 4 | -2^31 ~ 2^31-1 | 32位有符号整数 |
| Uint32Array | 4 | 0 ~ 2^32-1 | 32位无符号整数 |
| Float32Array | 4 | IEEE 754 | 32位浮点数 |
| Float64Array | 8 | IEEE 754 | 64位浮点数 |
创建 TypedArray#
// 方式1:指定长度const int8 = new Int8Array(4)console.log(int8) // Int8Array [0, 0, 0, 0]
// 方式2:从数组创建const uint8 = new Uint8Array([1, 2, 3, 4])console.log(uint8) // Uint8Array [1, 2, 3, 4]
// 方式3:从 ArrayBuffer 创建const buffer = new ArrayBuffer(8)const int16 = new Int16Array(buffer)console.log(int16.length) // 4(8字节/2字节每元素)
// 方式4:从 ArrayBuffer 的一部分创建const view = new Int32Array(buffer, 0, 2) // 偏移0,长度2
// 方式5:从另一个 TypedArray 创建const float32 = new Float32Array(uint8)基本操作#
const arr = new Uint8Array(4)
// 读写元素arr[0] = 255arr[1] = 128console.log(arr[0]) // 255
// 溢出处理arr[2] = 256 // Uint8 最大 255console.log(arr[2]) // 0(256 % 256)
arr[3] = -1console.log(arr[3]) // 255(-1 在无符号中表示最大值)
// 属性arr.length // 4arr.byteLength // 4arr.buffer // ArrayBufferarr.byteOffset // 0Uint8ClampedArray 特殊性#
溢出时钳位到边界值而不是取模:
const uint8 = new Uint8Array([256, -1])console.log(uint8) // [0, 255](取模)
const clamped = new Uint8ClampedArray([256, -1])console.log(clamped) // [255, 0](钳位)
// 常用于图像处理,避免颜色值溢出数组方法#
TypedArray 支持大部分数组方法:
const arr = new Uint8Array([3, 1, 4, 1, 5, 9])
// 支持的方法arr.slice(0, 3) // Uint8Array [3, 1, 4]arr.map((x) => x * 2) // Uint8Array [6, 2, 8, 2, 10, 18]arr.filter((x) => x > 2) // Uint8Array [3, 4, 5, 9]arr.reduce((a, b) => a + b, 0) // 23arr.find((x) => x > 5) // 9arr.indexOf(4) // 2arr.includes(5) // truearr.sort() // Uint8Array [1, 1, 3, 4, 5, 9]
// 不支持的方法(会改变长度)// arr.push(1); // 不存在// arr.pop(); // 不存在// arr.splice(); // 不存在TypedArray 特有方法#
const arr1 = new Uint8Array([1, 2, 3, 4])const arr2 = new Uint8Array([5, 6])
// set:复制数据到指定位置arr1.set(arr2, 2)console.log(arr1) // [1, 2, 5, 6]
// subarray:创建视图(共享内存)const sub = arr1.subarray(1, 3)console.log(sub) // [2, 5]sub[0] = 99console.log(arr1) // [1, 99, 5, 6](原数组也变了)DataView#
DataView 提供更灵活的二进制数据访问,支持:
- 任意字节偏移
- 指定字节序(大端/小端)
- 混合数据类型
const buffer = new ArrayBuffer(8)const view = new DataView(buffer)
// 写入数据view.setInt8(0, 127) // 位置0,写入int8view.setInt16(1, 1000, true) // 位置1,写入int16,小端view.setFloat32(4, 3.14, true) // 位置4,写入float32,小端
// 读取数据view.getInt8(0) // 127view.getInt16(1, true) // 1000view.getFloat32(4, true) // 3.140000104904175字节序#
const buffer = new ArrayBuffer(4)const view = new DataView(buffer)
// 写入 0x12345678view.setUint32(0, 0x12345678, false) // 大端(网络字节序)new Uint8Array(buffer) // [0x12, 0x34, 0x56, 0x78]
view.setUint32(0, 0x12345678, true) // 小端(x86)new Uint8Array(buffer) // [0x78, 0x56, 0x34, 0x12]实际应用#
图像处理#
// 获取 Canvas 像素数据const canvas = document.querySelector('canvas')const ctx = canvas.getContext('2d')const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height)const pixels = imageData.data // Uint8ClampedArray
// 反转颜色for (let i = 0; i < pixels.length; i += 4) { pixels[i] = 255 - pixels[i] // R pixels[i + 1] = 255 - pixels[i + 1] // G pixels[i + 2] = 255 - pixels[i + 2] // B // pixels[i + 3] 是 Alpha,保持不变}
ctx.putImageData(imageData, 0, 0)文件处理#
// 读取文件为 ArrayBufferasync function readFile(file) { const buffer = await file.arrayBuffer() return new Uint8Array(buffer)}
// 检查 PNG 文件签名function isPNG(buffer) { const signature = [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a] const header = new Uint8Array(buffer, 0, 8) return signature.every((byte, i) => byte === header[i])}Base64 编解码#
// ArrayBuffer 转 Base64function arrayBufferToBase64(buffer) { const bytes = new Uint8Array(buffer) let binary = '' for (const byte of bytes) { binary += String.fromCharCode(byte) } return btoa(binary)}
// Base64 转 ArrayBufferfunction base64ToArrayBuffer(base64) { const binary = atob(base64) const buffer = new ArrayBuffer(binary.length) const bytes = new Uint8Array(buffer) for (let i = 0; i < binary.length; i++) { bytes[i] = binary.charCodeAt(i) } return buffer}网络通信#
// 构建二进制协议数据包function createPacket(type, payload) { const payloadBytes = new TextEncoder().encode(payload) const buffer = new ArrayBuffer(4 + payloadBytes.length) const view = new DataView(buffer)
view.setUint8(0, type) // 消息类型 view.setUint8(1, 0) // 版本 view.setUint16(2, payloadBytes.length, true) // 长度(小端)
new Uint8Array(buffer, 4).set(payloadBytes) return buffer}
// 解析数据包function parsePacket(buffer) { const view = new DataView(buffer) const type = view.getUint8(0) const version = view.getUint8(1) const length = view.getUint16(2, true) const payload = new TextDecoder().decode(new Uint8Array(buffer, 4, length))
return { type, version, payload }}WebGL 顶点数据#
// 创建顶点缓冲const vertices = new Float32Array([ // x, y, z -1.0, -1.0, 0.0, 1.0, -1.0, 0.0, 0.0, 1.0, 0.0,])
// 创建索引缓冲const indices = new Uint16Array([0, 1, 2])
// 传递给 WebGLconst gl = canvas.getContext('webgl')const vertexBuffer = gl.createBuffer()gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer)gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW)性能优势#
// 普通数组 vs TypedArray 性能对比const size = 1000000
// 普通数组console.time('Array')const arr = new Array(size)for (let i = 0; i < size; i++) arr[i] = ilet sum1 = 0for (let i = 0; i < size; i++) sum1 += arr[i]console.timeEnd('Array')
// TypedArrayconsole.time('TypedArray')const typed = new Int32Array(size)for (let i = 0; i < size; i++) typed[i] = ilet sum2 = 0for (let i = 0; i < size; i++) sum2 += typed[i]console.timeEnd('TypedArray')
// TypedArray 通常快 2-10 倍小结#
| 类型 | 用途 |
|---|---|
| ArrayBuffer | 底层二进制数据容器 |
| TypedArray | 类型化视图,高效操作同类型数据 |
| DataView | 灵活视图,支持混合类型和字节序 |
TypedArray 是处理二进制数据的利器,在图像处理、音视频、网络通信、WebGL 等场景中不可或缺。