各位靓仔靓女们,早上好/下午好/晚上好!我是你们的老朋友,今天咱们来聊聊JavaScript里一个有点“硬核”但又非常实用的家伙——Typed Arrays。
这玩意儿,说实话,一开始看到名字,我也有点懵圈。啥叫“类型化的数组”?听起来就像是数组穿了西装,戴了领带,瞬间变身精英人士。但实际上,它比这有趣多了。
一、Typed Arrays:告别JavaScript数组的“任性”
在JavaScript的世界里,普通的数组那是相当的“任性”。它可以放任何类型的数据,数字、字符串、对象,甚至是函数,一股脑儿塞进去都没问题。这很灵活,但对于某些对性能要求极高的场景,比如科学计算、图像处理、WebGPU等等,就显得效率不高了。
为什么呢?
- 类型不确定: JavaScript引擎需要时刻检查数组元素的类型,这会增加运行时的开销。
- 存储不紧凑: JavaScript数组在内存中不一定是连续存储的,这会导致访问速度变慢。
想象一下,你参加一个宴会,主办方说:“大家随便吃,想吃啥拿啥!” 结果你发现,甜点、主食、饮料混在一起,你需要不停地分辨哪个是你要的,效率自然就低了。
Typed Arrays就像是把宴会分成了不同的区域:甜点区、主食区、饮料区。每个区域只放特定类型的东西,这样你就可以直接去对应的区域拿你想要的,效率大大提升。
二、Typed Arrays家族成员:八大金刚闪亮登场
Typed Arrays并不是一个具体的类型,而是一系列构造函数的集合,每个构造函数对应一种特定的数据类型。 它们就像是八大金刚,各有神通:
构造函数 | 描述 | 存储大小(字节) | 范围(近似) |
---|---|---|---|
Int8Array | 8位有符号整数数组 | 1 | -128 到 127 |
Uint8Array | 8位无符号整数数组 | 1 | 0 到 255 |
Uint8ClampedArray | 8位无符号整数数组,值会被截取到0-255之间 | 1 | 0 到 255 |
Int16Array | 16位有符号整数数组 | 2 | -32768 到 32767 |
Uint16Array | 16位无符号整数数组 | 2 | 0 到 65535 |
Int32Array | 32位有符号整数数组 | 4 | -2147483648 到 2147483647 |
Uint32Array | 32位无符号整数数组 | 4 | 0 到 4294967295 |
Float32Array | 32位浮点数数组(单精度) | 4 | 大约 ±1.18e-38 到 ±3.4e+38 |
Float64Array | 64位浮点数数组(双精度) | 8 | 大约 ±2.23e-308 到 ±1.80e+308 |
BigInt64Array | 64位有符号大整数数组 | 8 | -263 到 263-1 |
BigUint64Array | 64位无符号大整数数组 | 8 | 0 到 264-1 |
代码示例:创建和使用Typed Arrays
// 创建一个 Int8Array,长度为5
const int8Array = new Int8Array(5);
// 设置数组元素的值
int8Array[0] = -10;
int8Array[1] = 20;
int8Array[2] = 127;
int8Array[3] = -128;
int8Array[4] = 128; // 注意:128超出了 Int8Array 的范围,会被截断为 -128
console.log(int8Array); // Int8Array(5) [ -10, 20, 127, -128, -128 ]
// 创建一个 Float32Array,长度为3
const float32Array = new Float32Array([1.0, 2.5, 3.75]);
console.log(float32Array); // Float32Array(3) [ 1, 2.5, 3.75 ]
// 创建一个 Uint8ClampedArray
const clampedArray = new Uint8ClampedArray([256, -1, 128]);
console.log(clampedArray); // Uint8ClampedArray(3) [255, 0, 128] 超过255会被clamp到255,小于0会被clamp到0
// 获取数组的长度
console.log(int8Array.length); // 5
// 获取数组的元素大小(以字节为单位)
console.log(int8Array.BYTES_PER_ELEMENT); // 1
console.log(float32Array.BYTES_PER_ELEMENT); // 4
三、ArrayBuffer:Typed Arrays的“幕后大哥”
Typed Arrays 并不是直接存储数据的,它们只是对 ArrayBuffer 的一种视图。ArrayBuffer 才是真正存储二进制数据的对象。你可以把 ArrayBuffer 想象成一块原始的内存区域,而 Typed Arrays 则是对这块区域的不同解释方式。
代码示例:ArrayBuffer和Typed Arrays的配合
// 创建一个 ArrayBuffer,大小为16字节
const buffer = new ArrayBuffer(16);
// 创建一个 Int32Array,它指向 ArrayBuffer 的前4个字节
const int32View = new Int32Array(buffer, 0, 1); // (buffer, byteOffset, length)
//创建一个 Float32Array,它指向 ArrayBuffer 的5-8个字节
const float32View = new Float32Array(buffer, 4, 1);
// 通过 Int32Array 修改 ArrayBuffer 的值
int32View[0] = 12345;
// 通过 Float32Array 修改 ArrayBuffer 的值
float32View[0] = 3.14159;
// 创建一个 DataView,它可以访问 ArrayBuffer 的任意字节
const dataView = new DataView(buffer);
// 读取 ArrayBuffer 的第一个字节
const firstByte = dataView.getInt8(0);
console.log(firstByte); // 输出:57
console.log(int32View); // Int32Array(1) [ 12345 ]
console.log(float32View); // Float32Array(1) [ 3.141590118408203 ]
从上面的例子可以看出,ArrayBuffer 就像一个“容器”,Typed Arrays 就像是“刻度尺”,你可以用不同的刻度尺去测量同一个容器,得到不同的结果。
四、DataView:灵活访问ArrayBuffer的“瑞士军刀”
DataView 提供了更灵活的方式来访问 ArrayBuffer 中的数据。你可以指定读取或写入数据的类型和字节序(大端或小端)。
代码示例:DataView的使用
const buffer = new ArrayBuffer(8);
const dataView = new DataView(buffer);
// 设置 ArrayBuffer 的前4个字节为一个32位整数(大端字节序)
dataView.setInt32(0, 0x12345678, false); // (byteOffset, value, littleEndian)
// 读取 ArrayBuffer 的前4个字节为一个32位整数(大端字节序)
const value = dataView.getInt32(0, false);
console.log(value); // 输出:305419896 (0x12345678)
// 设置 ArrayBuffer 的后4个字节为一个32位浮点数
dataView.setFloat32(4, 3.14159, true); // 小端字节序
// 读取 ArrayBuffer 的后4个字节为一个32位浮点数
const floatValue = dataView.getFloat32(4, true);
console.log(floatValue); // 输出:3.141590118408203
DataView 就像一把瑞士军刀,可以让你精细地操作 ArrayBuffer 中的每一个字节。
五、Typed Arrays在科学计算中的应用
科学计算通常需要处理大量的数据,对性能要求很高。Typed Arrays 可以提供更高效的数据存储和访问方式,从而提升计算速度。
场景:向量加法
假设我们需要对两个包含大量元素的向量进行加法运算。
普通JavaScript数组:
function vectorAdd(a, b) {
const result = [];
for (let i = 0; i < a.length; i++) {
result[i] = a[i] + b[i];
}
return result;
}
const a = new Array(1000000).fill(1);
const b = new Array(1000000).fill(2);
console.time('Array Add');
const sum = vectorAdd(a, b);
console.timeEnd('Array Add'); // 约 10-20ms (取决于机器)
Typed Arrays:
function typedVectorAdd(a, b) {
const result = new Float32Array(a.length);
for (let i = 0; i < a.length; i++) {
result[i] = a[i] + b[i];
}
return result;
}
const a = new Float32Array(1000000).fill(1);
const b = new Float32Array(1000000).fill(2);
console.time('TypedArray Add');
const typedSum = typedVectorAdd(a, b);
console.timeEnd('TypedArray Add'); // 约 2-5ms (取决于机器)
可以看到,使用 Typed Arrays 后,计算速度明显提升。这是因为 Typed Arrays 在内存中是连续存储的,而且避免了 JavaScript 引擎的类型检查开销。
六、Typed Arrays在WebGPU中的应用
WebGPU 是一种用于在 Web 平台上进行高性能图形和计算的 API。它允许 JavaScript 代码直接访问 GPU 的底层功能,从而实现更强大的图形渲染和并行计算能力。
Typed Arrays 在 WebGPU 中扮演着重要的角色。WebGPU 使用 Typed Arrays 来传递数据给 GPU,例如:
- 顶点数据: 描述 3D 模型的顶点位置、颜色、法线等信息。
- 索引数据: 定义 3D 模型的三角形连接方式。
- 纹理数据: 存储图像数据,用于渲染 3D 模型的表面。
- Uniform 数据: 存储着色器程序需要的常量值,例如光照参数、变换矩阵等。
WebGPU要求输入的数据必须是特定的Typed Arrays,这样才能保证数据格式的正确性和效率。
代码示例(简化):WebGPU中使用Typed Arrays
// 创建一个 ArrayBuffer,用于存储顶点数据
const vertexBufferData = new Float32Array([
-0.5, -0.5, 0.0, // 顶点1的 x, y, z 坐标
0.5, -0.5, 0.0, // 顶点2的 x, y, z 坐标
0.0, 0.5, 0.0 // 顶点3的 x, y, z 坐标
]);
// 创建一个 GPUBuffer,并将顶点数据复制到 GPU 内存中
const vertexBuffer = device.createBuffer({
size: vertexBufferData.byteLength,
usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
mappedAtCreation: true, //buffer创建时就map,允许立即写入
});
new Float32Array(vertexBuffer.getMappedRange()).set(vertexBufferData);
vertexBuffer.unmap();
// 在 WebGPU 渲染管线中,将 vertexBuffer 绑定到顶点着色器的输入
const vertexBufferLayout = {
arrayStride: 12, // 每个顶点占用 12 个字节 (3 个 float32)
attributes: [{
shaderLocation: 0, // 顶点着色器中 attribute 的位置
offset: 0, // 从顶点数据的起始位置开始
format: 'float32x3' // 数据格式为 float32 向量 (3 个分量)
}]
};
在这个例子中,我们使用 Float32Array
来存储顶点数据,并将它传递给 WebGPU 的 GPUBuffer
。WebGPU 通过 vertexBufferLayout
来了解顶点数据的格式和布局。
七、Typed Arrays的注意事项
- 类型固定: 一旦创建了 Typed Array,它的类型就不能改变了。如果你需要存储不同类型的数据,需要创建多个 Typed Array。
- 长度固定: Typed Array 的长度也是固定的,不能像普通数组那样动态地添加或删除元素。如果需要动态调整大小,需要创建新的 Typed Array 并复制数据。
- 不是真正的数组: Typed Arrays 并不是真正的 JavaScript 数组,它们没有
push
、pop
等方法。但它们可以使用数组的下标访问元素,并且具有length
属性。 - 拷贝问题: Typed Arrays的拷贝需要注意深拷贝和浅拷贝的问题。
slice()
方法返回的是一个新的 Typed Array,但是它共享底层的 ArrayBuffer。如果需要完全独立的拷贝,需要手动创建新的 ArrayBuffer 并复制数据。
八、总结
Typed Arrays 是 JavaScript 中用于处理二进制数据的利器。它们可以提供更高效的数据存储和访问方式,从而提升性能。在科学计算、图像处理、WebGPU 等对性能要求极高的场景中,Typed Arrays 发挥着重要的作用。
虽然 Typed Arrays 的学习曲线可能稍微陡峭,但是一旦掌握了它的基本概念和用法,你就可以在 JavaScript 中编写出更高效、更强大的代码。
好了,今天的讲座就到这里。希望大家能够对 Typed Arrays 有更深入的了解。记住,掌握了 Typed Arrays,你就能让你的 JavaScript 代码“硬”起来! 下次有机会再和大家分享更多好玩的技术。拜拜!