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 可能使用
DoublePacked
或Smi
(小整数)格式,连续存储。 - 混合类型降级:添加不同类型元素(如数字+字符串)时,可能退化为慢数组,降低性能。
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 数组的内存存储由引擎动态管理,根据元素类型、分布等因素在连续内存和哈希表间切换,开发者可通过编写规范代码利用优化策略提升性能。