1. ​基本特性

JavaScript 数组是动态的,支持随时增减长度,元素类型可异构。与传统静态类型数组(如 C++)不同,其内存分配更灵活,具体实现因引擎而异(以 V8 为例)。


2. ​V8 引擎的两种存储模式

  • 快数组(Fast Elements)​​:

    • 连续内存分配​:元素类型一致且排列紧密时,使用连续内存块,类似传统数组。
    • 高效访问​:通过索引直接计算内存地址,时间复杂度 O(1)。
    • 适用场景​:密集且类型一致的数组(如 [1, 2, 3])。
  • 慢数组(Slow Elements)​​:

    • 哈希表(字典)存储​:元素稀疏、类型混杂或存在空洞时,退化为类似对象的哈希表,键为字符串化索引。
    • 访问开销​:时间复杂度接近 O(1)(哈希表平均情况),但比快数组慢。
    • 适用场景​:[1, , 3](空洞)或大跨度索引(如 arr[10000] = 1)。

3. ​动态扩容策略

  • 容量调整​:当元素数量超过当前容量时,V8 会按比例(如双倍)扩容,复制旧数据到新内存块。
  • 缩容优化​:删除大量元素后,可能触发缩容以节省内存。
  • 均摊时间复杂度​:动态扩容使插入操作均摊时间复杂度为 O(1)。

4. ​元素类型的影响

  • 类型一致优化​:若元素均为数字,V8 可能使用 DoublePackedSmi(小整数)格式,连续存储。
  • 混合类型降级​:添加不同类型元素(如数字+字符串)时,可能退化为慢数组,降低性能。

5. ​快慢数组的转换条件

  • 快 → 慢​:

    • 添加非连续索引(如 arr[1000] = 1)。
    • 元素类型变得不一致。
    • 使用 delete 删除元素导致空洞。
  • 慢 → 快​:

    • 数组重新变得紧凑(如填充空洞)。
    • 元素类型恢复一致。

6. ​与对象的关系

  • 数组是特殊对象​:typeof [] === 'object',索引可视为字符串键(arr[0] 等价 arr['0'])。
  • 原型方法​:数组继承 Array.prototype 的方法(如 push, pop),而普通对象无这些方法。

7. ​性能优化建议

  • 优先使用连续索引​:避免创建空洞(如 arr.length = 10000 后赋值)。
  • 保持类型一致​:如全数字或全字符串,以利用快数组优化。
  • 避免频繁扩容​:初始化时预估大小(如 new Array(100))。

8. ​示例对比

// 快数组(连续,类型一致)
const fastArr = [1, 2, 3]; // 连续内存存储
 
// 慢数组(稀疏,类型混杂)
const slowArr = [];
slowArr[0] = 1;
slowArr[10000] = 2; // 转换为字典存储
slowArr[1] = 'string'; // 进一步降级

9. ​特殊类型数组

  • 类型化数组(TypedArray)​​:如 Int32Array,内存连续且类型固定,类似传统数组,用于二进制数据处理。
  • 与普通数组区别​:类型化数组长度固定,不支持非数字类型,性能更优。

10. ​其他引擎的差异

  • 不同实现​:Chrome(V8)、Firefox(SpiderMonkey)、Safari(JavaScriptCore)可能有细微差异,但核心思路相似(动态类型处理)。

总结​:JavaScript 数组的内存存储由引擎动态管理,根据元素类型、分布等因素在连续内存和哈希表间切换,开发者可通过编写规范代码利用优化策略提升性能。