一句话通俗解释JavaScript系列 - 定型数组

172 阅读5分钟

🔑 定型数组(Typed Arrays)是一种高效的数据结构,旨在提升JavaScript与原生库(如进行图形处理的WebGL)之间数据传输的效率,允许JavaScript直接操作内存中的二进制数据,开发者可以创建固定类型的数组,如整数或浮点数数组,这些数组的表现形式更接近于底层硬件,从而使得数据操作更加快速和内存效率更高。

一些特点

  • 直接操作内存

    • 定型数组允许JavaScript代码直接读写二进制数据,这一点与C或C++中的数组和指针操作类似,能够提升处理大量数值数据的效率。
    • 相比于JavaScript的常规数组,定型数组因为绕过了JavaScript的高级抽象,而直接操作内存,从而实现了更高的性能。
    • 定型数组背后的核心是ArrayBuffer对象,它代表了内存中的一块原始二进制数据区域,而定型数组则提供了一种读写这块内存的接口。
  • 固定类型

    • JavaScript的常规数组是动态类型的,可以存储任意类型的数据。而定型数组只能存储单一类型的数据,如Int32Array仅存储32位整数。
    • 这种固定类型的特性使得定型数组的行为更接近于Java或C#中的数组,其中数组的元素类型在声明时就已确定。
  • 字节序

    • 定型数组遵循系统的原生字节序(大端或小端)。这一点在进行网络传输或文件I/O操作时尤其重要,因为不同的系统可能有不同的字节序。
    • 这与Python的struct模块类似,后者也允许开发者指定字节序,但在JavaScript中,字节序的处理对于开发者来说是透明的。
  • 与WebGL的紧密集成

    • 定型数组与WebGL等Web标准紧密集成,使得在网页中渲染复杂的3D图形成为可能。
    • 这种集成让JavaScript在Web图形领域的应用与C++中使用OpenGL或DirectX的方式有了直接的比较基础,尽管后者更为底层和强大,但JavaScript通过定型数组和WebGL提供了一个更为简便和可访问的3D图形解决方案。

以Todo List为例:

如果Todo List项目涉及到性能敏感的数据处理,例如批量操作大量Todo项的状态更新、统计或者加密解密,那么定型数组就可能派上用场。

例子1:批量更新Todo项状态

假设我们有一个包含成千上万个Todo项的列表,每个Todo项有一个状态值,表示该项是待完成(0)还是已完成(1)。如果我们想要批量更新这些Todo项的状态,可以使用Uint8Array来实现,因为它足以表示这两种状态,并且操作起来非常高效。

初始化Todo状态列表

首先初始化一个定型数组来表示所有Todo项的状态:

// 假设有10000个Todo项
const todosCount = 10000;
// 使用Uint8Array初始化所有Todo项的状态为0(待完成)
const todosStatus = new Uint8Array(todosCount);

批量更新状态

接下来定义一个函数来批量更新Todo项的状态。例如,我们想将前5000项标记为已完成(状态设置为1):

function markAsCompleted(startIndex, endIndex) {
    for (let i = startIndex; i <= endIndex; i++) {
        todosStatus[i] = 1; // 将状态更新为1(已完成)
    }
}

// 将前5000个Todo项标记为已完成
markAsCompleted(0, 4999);

这里使用Uint8Array而不是常规的JavaScript数组的好处在于:

  • 内存使用更高效Uint8Array为每个状态分配恰好1个字节,而常规数组可能会为相同的数据分配更多内存,尤其是当数组元素是数字时。
  • 性能更优:对于大量数据,定型数组的读写速度通常快于常规数组,这是因为它避免了JavaScript引擎的某些动态类型检查。

例子2: 使用定型数组存储Todo项的优先级

假设我们有一个Todo List,每个Todo项除了基本的文本描述外,还有一个1到5的优先级评分。我们可以使用Uint8Array来存储这些优先级,因为Uint8Array是用于存储8位无符号整数的定型数组,非常适合存储这种范围在1到5的小整数。

// 假设我们有5个Todo项,这是它们的优先级数组
const priorities = new Uint8Array([5, 3, 4, 1, 2]);

// 增加一个新的Todo项的优先级
const newPriority = 3; // 新Todo项的优先级
const updatedPriorities = new Uint8Array(priorities.length + 1);
updatedPriorities.set(priorities); // 复制现有优先级
updatedPriorities[updatedPriorities.length - 1] = newPriority; // 添加新优先级

console.log(updatedPriorities); // 输出更新后的优先级数组

首先创建了一个Uint8Array来存储初始的优先级。当我们需要添加一个新的Todo项时,我们创建了一个新的Uint8Array来存储更新后的优先级列表,然后使用.set()方法复制现有的优先级,最后添加新的优先级到数组末尾。

例子3: 加密存储Todo项

如果我们想要在客户端安全地存储Todo项,可以使用定型数组结合Web Crypto API来进行加密。

(async () => {
  // 假设这是我们需要加密的Todo项文本
  const todoText = "Complete the project documentation";

  // 将字符串转换为ArrayBuffer
  const encoder = new TextEncoder();
  const encoded = encoder.encode(todoText);

  // 生成密钥(异步操作)
  const key = await window.crypto.subtle.generateKey(
    {
      name: "AES-GCM",
      length: 256,
    },
    true, // 是否可导出
    ["encrypt", "decrypt"] // 用途
  );

  // 加密Todo文本(异步操作)
  const iv = window.crypto.getRandomValues(new Uint8Array(12)); // 初始向量
  window.crypto.subtle.encrypt(
    {
      name: "AES-GCM",
      iv: iv,
    },
    key,
    encoded
  ).then(encrypted => {
    // `encrypted` 是一个ArrayBuffer,包含了加密后的数据
    console.log(new Uint8Array(encrypted)); // 可以转换为Uint8Array来查看或存储
    // 这里可以继续进行解密步骤...
  });
})();

首先使用TextEncoder将待加密的字符串转换为ArrayBuffer,然后使用Web Crypto API的encrypt方法对数据进行加密。加密函数返回的encrypted也是一个ArrayBuffer,我们可以将其转换为Uint8Array以便于查看或存储。