前言
ECMAScript标准是深入学习JavaScript原理最好的资料,没有其二。
通过增加对ECMAScript语言的理解,理解javascript现象后面的逻辑,提升个人编码能力。
欢迎关注和订阅专栏 重学前端-ECMAScript协议上篇
快慢模式
JS中的数组在V8中对应的是 JSArray, 其源码有一段很直白的注释

- 快模式: 采用FixedArray来存储,
length <= elements.length()
。push 和 pop 操作会增加或者缩小数组。 - 慢模式: 用数字为键的哈希表(HashTable)来存储。
这里提到了一个elements
,可以通过 %DebugPrint
打印数组的信息来查看:
比如采用如下代码:
javascript
var array1 = [0,1,2];
%DebugPrint(array1);
array1[1027] = 1027;
%DebugPrint(array1);
在输出的内容中,有elements字段,第一次打印的时候其为FixedArray, 第二次打印已经变为NumberDictonary。
javascript
DebugPrint: 000001074AA7FB89: [JSArray]
- map: 0x03702b7032d9 <Map(PACKED_SMI_ELEMENTS)> [FastProperties]
- prototype: 0x0134264c5971 <JSArray[0]>
- elements: 0x015c079b7139 <FixedArray[3]> [PACKED_SMI_ELEMENTS (COW)]
- length: 3
- properties: 0x01ce95b01309 <FixedArray[0]>
- All own properties (excluding elements): {
000001CE95B04D59: [String] in ReadOnlySpace: #length: 0x00cbc4501189 <AccessorInfo> (const accessor descriptor)
DebugPrint: 000001074AA7FB89: [JSArray]
- map: 0x03702b732211 <Map(DICTIONARY_ELEMENTS)> [FastProperties]
- prototype: 0x0134264c5971 <JSArray[0]>
- elements: 0x01074aa7fc39 <NumberDictionary[28]> [DICTIONARY_ELEMENTS]
- length: 1028
- properties: 0x01ce95b01309 <FixedArray[0]>
- All own properties (excluding elements): {
000001CE95B04D59: [String] in ReadOnlySpace: #length: 0x00cbc4501189 <AccessorInfo> (const accessor descriptor)
这快慢模式,体现的其实是两种不同的思想:
- 快模式 : 空间换时间
其会申请连续的内存,提高速度。
- 慢模式: 时间换空间
其不需要申请连续的内容,节约内存,但是降低了速度。
数组扩容
数组容量不够实用的时候,会进行扩容,扩容方法如下:JSObject::NewElementsCapacity。
诶,为啥是 JSObject的方法呢? 数组也是对象哦。
javascript
// https://github.com/v8/v8/blob/9.3.345/src/objects/js-objects.h#L558
static uint32_t NewElementsCapacity(uint32_t old_capacity) {
// old_capacity + (old_capacity >> 1) + 16
return old_capacity + (old_capacity >> 1) + kMinAddedElementsCapacity;
}
// https://github.com/v8/v8/blob/9.3.345/src/objects/js-objects.h#L555
static const uint32_t kMinAddedElementsCapacity = 16;
结合上面的图来看,这里的 old_capacity
并不是 旧容量, 而是 新的最大索引值
+ 1,或者新的数组长度,不过还是这么称呼她吧。
新容积 = 旧容量+ (旧容量>> 1) + 16
示例1
一起来看个例子
- 一个初始化容量为 5的数组,
- 设置索引 6 为 6, 套入公式,其容量扩容到
(6 + 1) + ( (6+1) >>1 ) + 16
= 26
javascript
// 容量:5
var array1 = [0, 1, 2, 3, 4];
console.log('array1.length:', array1.length);
%DebugPrint(array1);
// 旧容量+ (旧容量>> 1) + 16 = (6 + 1) + ( (6+1) >>1 ) + 16 = 26
array1[6] = 6;
%DebugPrint(array1);
扩容前:5

扩容后:26

示例2
哦,懂了,懂了,懂了,少年,你没懂。接着看
javascript
var array1 = new Array();
console.log('array1.length:', array1.length);
// 容量:4
%DebugPrint(array1);
// ?? 旧容量+ (旧容量>> 1) + 16 = (5000 + 1) + ((5000 + 1) >>1 ) + 16 = 7517
array1[5000] = 5000;
%DebugPrint(array1);
new Array()
数组长度为0, 容量却是 4 , 这是 js-array.h kPreallocatedArrayElements变量定义的,学到没?


诶,这怎么不按照规矩扩容了呢? 仔细看 这已经不是 数组了,而是字典呢, 变成慢数组了。

所以呢:
- 之前提到的扩容,不是一定能得到生效的,如果扩容后还是快数组,是会生效的,但是
- 从上面的示例也看到了,快数组扩容后,可能转为慢数组。
那么接下来,一起探究。。。。。。
快转慢
1024 :快数组新增的索引与原数组长度的差值大于等于1024,快数组会被转换会慢数组
v8 js-objects.h 里方法 ShouldConvertToSlowElements 就是做这个判断。返回true, 表示应该转为慢数组。
只关注返回true部分的代码,代码变量JSObject::kMaxGap
位于 github.com/v8/v8/blob/..., 其值为1024。
javascript
// Maximal gap that can be introduced by adding an element beyond
// the current elements length.
static const uint32_t kMaxGap = 1024;
注意 (1) 新索引 - 数组长度 >= 1024
部分, 代码15行
javascript
static inline bool ShouldConvertToSlowElements(JSObject object,
uint32_t capacity,
uint32_t index,
uint32_t* new_capacity) {
static_assert(JSObject::kMaxUncheckedOldFastElementsLength <=
JSObject::kMaxUncheckedFastElementsLength);
if (index < capacity) {
*new_capacity = capacity;
return false;
}
// **(1) 新索引 - 数组长度 >= 1024**
if (index - capacity >= JSObject::kMaxGap) return true;
// (index + 1) + ((index + 1) >> 1) + 16
*new_capacity = JSObject::NewElementsCapacity(index + 1);
DCHECK_LT(index, *new_capacity);
if (*new_capacity <= JSObject::kMaxUncheckedOldFastElementsLength ||
(*new_capacity <= JSObject::kMaxUncheckedFastElementsLength &&
ObjectInYoungGeneration(object))) {
return false;
}
return ShouldConvertToSlowElements(object.GetFastElementsUsage(),
*new_capacity);
}
// https://github.com/v8/v8/blob/9.3.345/src/objects/js-objects.h#L558
static uint32_t NewElementsCapacity(uint32_t old_capacity) {
// (old_capacity + 50%) + kMinAddedElementsCapacity
return old_capacity + (old_capacity >> 1) + kMinAddedElementsCapacity;
}
// https://github.com/v8/v8/blob/9.3.345/src/objects/js-objects.h#L555
static const uint32_t kMinAddedElementsCapacity = 16;
下面以一个长度为3的数组为例, 3 + 1024 = 1027
, 如果新的索引值大于等于1027, 数组类型即会发生变化。
如下分别以新索引值为1026和1027来测试:
新索引 1026:(FixedArray)
javascript
var array1 = [0,1,2];
array1[1026] = 1026;
%DebugPrint(array1);
javascript
DebugPrint: 000000A96B7BFB91: [JSArray]
- map: 0x03c9db003291 <Map(HOLEY_SMI_ELEMENTS)> [FastProperties]
- prototype: 0x025eba505971 <JSArray[0]>
- elements: 0x03d1638c1119 <FixedArray[1556]> [HOLEY_SMI_ELEMENTS]
- length: 1027
新索引 1027:(NumberDictionary)
javascript
var array1 = [0,1,2];
array1[1027] = 1027;
%DebugPrint(array1);
javascript
DebugPrint: 0000036B3FBBFB99: [JSArray]
- map: 0x019d429b2211 <Map(DICTIONARY_ELEMENTS)> [FastProperties]
- prototype: 0x001664b05971 <JSArray[0]>
- elements: 0x036b3fbbfc49 <NumberDictionary[28]> [DICTIONARY_ELEMENTS]
- length: 1028
可以看到新索引值为1027的时候, elements变为了 NumberDictionary
,而不是原来的 FixedArray
, 快转慢了。
500 和 5000
注意:这个数值扩容后的容量。
扩容后的容量, 满足下面条件之一,不会快转慢:
- 如果扩容后容量小于等于 500 (JSObject::kMaxUncheckedOldFastElementsLength)
- 如果扩容后容量小于等于 5000 (JSObject::kMaxUncheckedFastElementsLength) 并且 在新生代。
垃圾回收的新生代存放的是: 新创建的对象,小对象,短生命周期对象,存活时间短的对象 。
数组呢,一次垃圾回收后,是可能可以从新生代 转移到 老生代的。
注意19,20行
javascript
static inline bool ShouldConvertToSlowElements(JSObject object,
uint32_t capacity,
uint32_t index,
uint32_t* new_capacity) {
static_assert(JSObject::kMaxUncheckedOldFastElementsLength <=
JSObject::kMaxUncheckedFastElementsLength);
if (index < capacity) {
*new_capacity = capacity;
return false;
}
//(1) 新索引 - 数组长度 >= 1024
if (index - capacity >= JSObject::kMaxGap) return true;
// (index + 1) + ((index + 1) >> 1) + 16
*new_capacity = JSObject::NewElementsCapacity(index + 1);
DCHECK_LT(index, *new_capacity);
// 新容积 <= 500 不转为慢数组
// 或者 新容积 <= 5000 并且 在新生代
if (*new_capacity <= JSObject::kMaxUncheckedOldFastElementsLength ||
(*new_capacity <= JSObject::kMaxUncheckedFastElementsLength &&
ObjectInYoungGeneration(object))) {
return false;
}
return ShouldConvertToSlowElements(object.GetFastElementsUsage(),
*new_capacity);
}
// https://github.com/v8/v8/blob/10.7.116/src/objects/js-objects.h#L833
// Maximal length of fast elements array that won't be checked for
// being dense enough on expansion.
static const int kMaxUncheckedFastElementsLength = 5000;
// https://github.com/v8/v8/blob/10.7.116/src/objects/js-objects.h#L837
// Same as above but for old arrays. This limit is more strict. We
// don't want to be wasteful with long lived objects.
static const int kMaxUncheckedOldFastElementsLength = 500;
扩容后容量后数组容量(小于500)和 新生代数组(容量小于5000)做了特殊的处理,原因嘛,v8大家都懂,性能问题。
怎么去模拟非新生代呢? 其实也不难,可以添加一个额外的选项,让程序可以手动的执行gc
, 比如:
node --allow-natives-syntax --expose-gc "xxx.js"
500示例
这里知道扩容后是 500,为了推导出阈值,这里需要反推 index
的最大值。
根据扩容公式: 新容量 = (index + 1) + ((index + 1) >> 1) + 16

这里可以推导值,大概是 321 附近的值,
带入值 | 推导 | 最终值 |
---|---|---|
321 | (321 + 1) + ((321 + 1) >> 1) + 16 | 499 |
322 | (322 + 1) + ((322 + 1) >> 1) + 16 | 500 |
323 | (323 + 1) + ((323 + 1) >> 1) + 16 | 502 |
所以 阈值322, 如下的 323 代码执行后,数组转为了慢模式,elements 转为了 NumberDictionary
javascript
var array1 = [0,1,2];
array1[0] = -0;
// 垃圾回收, 触发转移到老生代
global.gc();
array1[323] = 323;
%DebugPrint(array1);
DebugPrint: 0000030A8F59ED79: [JSArray] in OldSpace
- map: 0x0049607eece9 <Map(DICTIONARY_ELEMENTS)> [FastProperties]
- prototype: 0x01c670ac5971 <JSArray[0]>
- elements: 0x0248426411b9 <NumberDictionary[28]> [DICTIONARY_ELEMENTS]
- length: 502
- properties: 0x016767481309 <FixedArray[0]>
- All own properties (excluding elements): {
0000016767484D59: [String] in ReadOnlySpace: #length: 0x000c6eb01189 <AccessorInfo> (const accessor descriptor)
如下不调用global.gc()
的代码执行不会发生改变,依旧还是快模式, 为什么呢?因为其后还有一个 5000 的限制。
javascript
var array1 = [0,1,2];
// array1[0] = -0;
// global.gc();
array1[501] = 501;
%DebugPrint(array1);
DebugPrint: 0000033A2BE3FAA9: [JSArray]
- map: 0x034368ac3291 <Map(HOLEY_SMI_ELEMENTS)> [FastProperties]
- prototype: 0x03a25e6c5971 <JSArray[0]>
- elements: 0x0118d1381119 <FixedArray[769]> [HOLEY_SMI_ELEMENTS]
- length: 502
- properties: 0x02f39dd81309 <FixedArray[0]>
- All own properties (excluding elements): {
000002F39DD84D59: [String] in ReadOnlySpace: #length: 0x01e9efe81189 <AccessorInfo> (const accessor descriptor)
5000 示例
根据扩容公式: 新容量 = (index + 1) + ((index + 1) >> 1) + 16

大概是 3321 附近的值,
带入值 | 推导 | 最终值 |
---|---|---|
3321 | (3321 + 1) + ((3321 + 1) >> 1) + 16 | 4999 |
3322 | (3322 + 1) + ((3322 + 1) >> 1) + 16 | 5000 |
3323 | (3323 + 1) + ((3323 + 1) >> 1) + 16 | 5002 |
索引值为3322。这里只是可以跳过新生代5000的限制,执行发生快转慢依旧是9倍那种判断的。
新最大索引值为3322, 扩容后小于等于 5000, 不发生快转慢。
javascript
var array1 = new Array(3000);
// 不会发生快转慢
// 扩容后的值 (3322 + 1) + ((3322 + 1) >> 1) + 16 = 5000 <= 5000
array1[3322] = 3322;
%DebugPrint(array1);
DebugPrint: 000001D0AAD41119: [JSArray]
- map: 0x0369b6743291 <Map(HOLEY_SMI_ELEMENTS)> [FastProperties]
- prototype: 0x028f6f7c5971 <JSArray[0]>
- elements: 0x01d0aad46f09 <FixedArray[5000]> [HOLEY_SMI_ELEMENTS]
- length: 3323
- properties: 0x027c40c81309 <FixedArray[0]>
新最大索引值为3323, 扩容后小于等于 5002, 发生快转慢, elements转为 NumberDictionary。 但是注意了,容量缺小了。
javascript
var array1 = new Array(3000);
// 发生快转慢
array1[3323] = 3323; // (3323 + 1) + ((3323 + 1) >> 1) + 16 = 5002
%DebugPrint(array1);
DebugPrint: 000003AD718C1119: [JSArray]
- map: 0x039647f32211 <Map(DICTIONARY_ELEMENTS)> [FastProperties]
- prototype: 0x0071fcdc5971 <JSArray[0]>
- elements: 0x03ad718c6f09 <NumberDictionary[16]> [DICTIONARY_ELEMENTS]
- length: 3324
- properties: 0x0345f1a41309 <FixedArray[0]>
这里是没有触发垃圾回收的,如果主动触发一次呢? 这里交给大家去尝试。
9倍:旧数组扩容后的容量 是 基于旧数组已使用容量计算而得的值 9倍及以上
9倍的计算逻辑源码位置: ShouldConvertToSlowElements(uint32_t used_elements, uint32_t new_capacity)。
与之前的 ShouldConvertToSlowElements(JSObject object, uint32_t capacity,uint32_t index, uint32_t* new_capacity) 不一样,后者内部再调用了前者,也就是前面的各种场景完毕之后,才轮到本小节这个9倍。
其核心主流程代码如下:
- 12 行: 新索引 - 数组长度 >= 1024
- 19,20行:500 和 5000
- 27行:9倍逻辑
javascript
static inline bool ShouldConvertToSlowElements(JSObject object,
uint32_t capacity,
uint32_t index,
uint32_t* new_capacity) {
static_assert(JSObject::kMaxUncheckedOldFastElementsLength <=
JSObject::kMaxUncheckedFastElementsLength);
if (index < capacity) {
*new_capacity = capacity;
return false;
}
//(1) 新索引 - 数组长度 >= 1024
if (index - capacity >= JSObject::kMaxGap) return true;
// (index + 1) + ((index + 1) >> 1) + 16
*new_capacity = JSObject::NewElementsCapacity(index + 1);
DCHECK_LT(index, *new_capacity);
// 新容积 <= 500 不转为慢数组
// 或者 新容积 <= 5000 并且 在新生代
if (*new_capacity <= JSObject::kMaxUncheckedOldFastElementsLength ||
(*new_capacity <= JSObject::kMaxUncheckedFastElementsLength &&
ObjectInYoungGeneration(object))) {
return false;
}
return ShouldConvertToSlowElements(object.GetFastElementsUsage(),
*new_capacity);
}
9倍核心代码和逻辑
javascript
static inline bool ShouldConvertToSlowElements(uint32_t used_elements,
uint32_t new_capacity) {
uint32_t size_threshold = NumberDictionary::kPreferFastElementsSizeFactor *
NumberDictionary::ComputeCapacity(used_elements) *
NumberDictionary::kEntrySize;
return size_threshold <= new_capacity;
}
// https://github.com/v8/v8/blob/10.7.116/src/objects/dictionary.h#L360
static const uint32_t kPreferFastElementsSizeFactor = 3;
// https://github.com/v8/v8/blob/10.7.116/src/objects/dictionary.h#L317
class NumberDictionaryShape : public NumberDictionaryBaseShape {
public:
static const int kPrefixSize = 1;
static const int kEntrySize = 3;
};
公式

其中:
- NumberDictionary::kPreferFastElementsSizeFactor = 3
- NumberDictionary::kEntrySize = 3
- used_elements 是一个数组实际使用的容量,比如长度为100, 只有一个非空元素,那么值为1。
- NumberDictionary::ComputeCapacity(used_elements)
基于旧数组已使用容量计算而得的值,简单说:就是算出不小 4, 且满足used_elements + (used_elements >> 1) > 2**n
的 最小2**n
值。
抽象把,举个例子:
以 used_elements为 40为例, ComputeCapacity 最终计算的值为 64。40 + (40 >> 1)
= 60- 最小大于60的
2**n
为 64 - kMinCapacity为4, Math.max(64, 4) 的结果为64
那么上面的 size_threshold =3 * 3 * ComputeCapacity(used_elements)
= 384
new_capacity
是扩容后的容量, 计算公式:旧容量+ (旧容量>> 1) + 16
把相关的代码放在一起,如下
javascript
// If the fast-case backing storage takes up much more memory than a dictionary
// backing storage would, the object should have slow elements.
// static
static inline bool ShouldConvertToSlowElements(uint32_t used_elements,
uint32_t new_capacity) {
uint32_t size_threshold = NumberDictionary::kPreferFastElementsSizeFactor *
NumberDictionary::ComputeCapacity(used_elements) *
NumberDictionary::kEntrySize;
return size_threshold <= new_capacity;
}
// https://github.com/v8/v8/blob/10.7.116/src/objects/dictionary.h#L360
static const uint32_t kPreferFastElementsSizeFactor = 3;
// https://github.com/v8/v8/blob/10.7.116/src/objects/dictionary.h#L266
class NumberDictionaryShape : public NumberDictionaryBaseShape {
public:
static const int kPrefixSize = 1;
static const int kEntrySize = 3;
};
// https://github.com/v8/v8/blob/10.7.116/src/objects/js-objects.h#L577
static const uint32_t kMinAddedElementsCapacity = 16;
// https://github.com/v8/v8/blob/9.3.345/src/objects/js-objects.h#L558
static uint32_t NewElementsCapacity(uint32_t old_capacity) {
// (old_capacity + 50%) + kMinAddedElementsCapacity (16)
return old_capacity + (old_capacity >> 1) + kMinAddedElementsCapacity;
}
// https://github.com/v8/v8/blob/10.7.116/src/objects/hash-table-inl.h#L116
// static
int HashTableBase::ComputeCapacity(int at_least_space_for) {
// Add 50% slack to make slot collisions sufficiently unlikely.
// See matching computation in HashTable::HasSufficientCapacityToAdd().
// Must be kept in sync with CodeStubAssembler::HashTableComputeCapacity().
int raw_cap = at_least_space_for + (at_least_space_for >> 1);
// 50 变成了 64, 100/120 变成了128, 200 变成了 256
int capacity = base::bits::RoundUpToPowerOfTwo32(raw_cap);
// kMinCapacity = 4 https://github.com/v8/v8/blob/10.7.116/src/objects/hash-table.h#L102
return std::max({capacity, kMinCapacity});
}
// https://github.com/v8/v8/blob/10.7.116/src/objects/hash-table.h#L102
static const int kMinCapacity = 4;
示例
这里的9倍和一个数组非空元素是关联起来的,此例子
- 新建一个容量为499的数组
- 然后填充部分元素,制造
use_elements
, 此例子 43是临界值,43发生快转慢,44不会发生。 空元素越少,越不容易发生快转慢。 - 垃圾回收
- 给索引499赋值,让数据扩容,触发快模式转换慢模式
以使用容量 used_elements 为43的:
size_threshold <= new_capacity => 576 <= 766 => true
触发扩容会发生快转慢,详细计算看代码备注:
javascript
var array1 = new Array(499);
// 填充数量,制造 used_elements = 43
for (let i = 0; i < 43; i++) {
array1[i] = i;
}
// 回收
global.gc();
console.log(array1.filter(v=> v!== null).length);
// https://github.com/v8/v8/blob/9.3.345/src/objects/js-objects-inl.h
// new_capacity: 500 + (500 << 1) + 16 = 766, 大于500
// NumberDictionary::ComputeCapacity(used_elements): 43 + (43 >> 1) => 64 => 64
// size_threshold : 3 * 3 * 64 = 576
// size_threshold <= new_capacity => 576 <= 766 => true
array1[499] = 499;
% DebugPrint(array1);
elements 转为为 NumberDictionary
used_elements 为44的时候:
size_threshold <= new_capacity => 1152 <= 766 => false
不会触发块转慢,详细计算看代码备注:
javascript
var array1 = new Array(499);
// 填充数量, 制造 use_elements
for (let i = 0; i < 44; i++) {
array1[i] = i;
}
// 回收
global.gc();
console.log(array1.filter(v=> v!== null).length);
// https://github.com/v8/v8/blob/9.3.345/src/objects/js-objects-inl.h
// new_capacity: (499 + 1) * 1.5 + 16 = 766, 大于500
// NumberDictionary::ComputeCapacity(used_elements): 44 + (44 >> 1) => 128 => 128
// size_threshold : 3 * 3 * 128 = 1152
// size_threshold <= new_capacity => 1152 <= 766 => false
array1[499] = 499;
% DebugPrint(array1);
// node --allow-natives-syntax --expose-gc "4.0 Array\1.4 to Slow 9m-02.js"
// console.log('process.versions:', process.versions)
elements 依旧是 FixedArray,快模式。
模式转换发生在赋值过程,不是初始化过程
而且是新的索引大于当前数组的能提供最大索引的时候
如下,初始化一个超大的数组, 对不超过当前数组能提供的最大索引赋值,是不会发生变化的。
所以,没事别初始化超大的数组。
javascript
var array1 = new Array(2 ** 20);
var a = 1;
var b = 2;
for (let i = 0; i < 1000; i++) {
i + 1;
}
global.gc();
array1[2 ** 20 - 2] = 0;
%DebugPrint(array1);
DebugPrint: 0000000EBABD99E9: [JSArray] in OldSpace
- map: 0x0369f2043291 <Map(HOLEY_SMI_ELEMENTS)> [FastProperties]
- prototype: 0x026202645971 <JSArray[0]>
- elements: 0x00df16881119 <FixedArray[1048576]> [HOLEY_SMI_ELEMENTS]
- length: 1048576
慢转快
数组也是可以从慢数组转为快书数组的, 源码位置 github.com/v8/v8/blob/... ShouldConvertToFastElements
方法,前面都是返回false, 主要看最后一行:
当慢数组容积 相比数组长度省小于等于50%,则转变成为快数组。
其意图也就是当一个数组的空元素由很多变到一定少的的时候,其会由慢模式转为快模式,想想也极其合理。
javascript
static bool ShouldConvertToFastElements(JSObject object,
NumberDictionary dictionary,
uint32_t index,
uint32_t* new_capacity) {
// If properties with non-standard attributes or accessors were added, we
// cannot go back to fast elements.
if (dictionary.requires_slow_elements()) return false;
// Adding a property with this index will require slow elements.
if (index >= static_cast<uint32_t>(Smi::kMaxValue)) return false;
if (object.IsJSArray()) {
Object length = JSArray::cast(object).length();
if (!length.IsSmi()) return false;
*new_capacity = static_cast<uint32_t>(Smi::ToInt(length));
} else if (object.IsJSArgumentsObject()) {
return false;
} else {
*new_capacity = dictionary.max_number_key() + 1;
}
*new_capacity = std::max(index + 1, *new_capacity);
uint32_t dictionary_size = static_cast<uint32_t>(dictionary.Capacity()) *
NumberDictionary::kEntrySize;
// Turn fast if the dictionary only saves 50% space.
return 2 * dictionary_size >= *new_capacity;
}
先造一个慢模式的数组:
依据: 快数组新增的索引与原容量(长度)的差值大于等于 1024,快数组会被转换会慢数组
javascript
var array1 = [0, 1, 2];
// 1028 1027 - 3 = 1024 会导致数组转为慢模式
array1[1027] = 1027;
之后,开始从索引3
开始填充数组,当数组里面的空元素数量越来越少的时候,其会有一个临界值,由慢模式再回恢复为快模式。
如下,分别会从索引3
填充到83
,84
,85
, 来验证。
javascript
// 填充数组
for (let index = 3; index < array1.length; index++) {
if(index === 83){
console.log('index:before', index);
%DebugPrint(array1);
console.log('index:after', index);
array1[index] = index;
%DebugPrint(array1);
break;
}
array1[index] = index;
}
先一起看看 index 分别为 83, 84, 85 赋值前后输出情况:
可以看到index = 84 赋值之后,字典进行扩容,容积从388升级到了772。
index = 83 填充前后:
javascript
index:before 83
DebugPrint: 0000026E925BFB39: [JSArray]
- map: 0x014f37fb2211 <Map(DICTIONARY_ELEMENTS)> [FastProperties]
- prototype: 0x03af0cc85971 <JSArray[0]>
- elements: 0x02a079dc1a79 <NumberDictionary[388]> [DICTIONARY_ELEMENTS]
- length: 1028
- properties: 0x02bee9e81309 <FixedArray[0]>
- All own properties (excluding elements): {
000002BEE9E84D59: [String] in ReadOnlySpace: #length: 0x003b01381189 <AccessorInfo> (const accessor descriptor)
index:after 83
DebugPrint: 0000026E925BFB39: [JSArray]
- map: 0x014f37fb2211 <Map(DICTIONARY_ELEMENTS)> [FastProperties]
- prototype: 0x03af0cc85971 <JSArray[0]>
- elements: 0x02a079dc1a79 <NumberDictionary[388]> [DICTIONARY_ELEMENTS]
- length: 1028
- properties: 0x02bee9e81309 <FixedArray[0]>
- All own properties (excluding elements): {
000002BEE9E84D59: [String] in ReadOnlySpace: #length: 0x003b01381189 <AccessorInfo> (const accessor descriptor)
index = 84 填充前后, 填充后elements的 NumberDictionary容积从388变为了772.
javascript
index:before 84
DebugPrint: 000000513DC3FB39: [JSArray]
- map: 0x016bd48b2211 <Map(DICTIONARY_ELEMENTS)> [FastProperties]
- prototype: 0x026e4bac5971 <JSArray[0]>
- elements: 0x03043df01a79 <NumberDictionary[388]> [DICTIONARY_ELEMENTS]
- length: 1028
- properties: 0x01dfc8cc1309 <FixedArray[0]>
- All own properties (excluding elements): {
000001DFC8CC4D59: [String] in ReadOnlySpace: #length: 0x035ef3b01189 <AccessorInfo> (const accessor descriptor
DebugPrint: 000000513DC3FB39: [JSArray]
- map: 0x016bd48b2211 <Map(DICTIONARY_ELEMENTS)> [FastProperties]
- prototype: 0x026e4bac5971 <JSArray[0]>
- elements: 0x03043df17821 <NumberDictionary[772]> [DICTIONARY_ELEMENTS]
- length: 1028
- properties: 0x01dfc8cc1309 <FixedArray[0]>
- All own properties (excluding elements): {
000001DFC8CC4D59: [String] in ReadOnlySpace: #length: 0x035ef3b01189 <AccessorInfo> (const accessor descriptor)
index = 85 填充前后, 填充后elements 从NumberDictionary[772]
变为了 FixedArray[1028]
javascript
index:before 85
DebugPrint: 000000C4298FFB39: [JSArray]
- map: 0x003207472211 <Map(DICTIONARY_ELEMENTS)> [FastProperties]
- prototype: 0x001f05285971 <JSArray[0]>
- elements: 0x0322b5a026a9 <NumberDictionary[772]> [DICTIONARY_ELEMENTS]
- length: 1028
- properties: 0x032a6c901309 <FixedArray[0]>
- All own properties (excluding elements): {
0000032A6C904D59: [String] in ReadOnlySpace: #length: 0x0247894c1189 <AccessorInfo> (const accessor descriptor)
index:after 85
DebugPrint: 000000C4298FFB39: [JSArray]
- map: 0x003207477c11 <Map(HOLEY_SMI_ELEMENTS)> [FastProperties]
- prototype: 0x001f05285971 <JSArray[0]>
- elements: 0x0322b5a19051 <FixedArray[1028]> [HOLEY_SMI_ELEMENTS]
- length: 1028
- properties: 0x032a6c901309 <FixedArray[0]>
- All own properties (excluding elements): {
0000032A6C904D59: [String] in ReadOnlySpace: #length: 0x0247894c1189 <AccessorInfo> (const accessor descriptor)
可以看到:
- index = 84 赋值之后,字典的容积从
388
增大到了772
。 - index = 85 赋值之后,elments从
NumberDictionary[772]
变为了FixedArray[1028]
, 即从慢数组变为了快数组。
一起来看看这个临界的变化:
index = 85 时( 此时字典dictionary_size 是 772):
- new_capacity = 1028
*new_capacity = std::max(index + 1, *new_capacity);
此时的 new_capacity 为数组长度1028, 当然比index + 1大,故值为1028. - dictionary_size = 772
代入:2 * dictionary_size >= *new_capacity
= 1544 >= 1028 , 值为true。 故会进行转换快数组。
index = 84 时( 此时字典dictionary_size 是 388):
- new_capacity = 1028
*new_capacity = std::max(index + 1, *new_capacity);
此时的 *new_capacity 为数组长度1028, 当然比index + 1大,故值为1028. - dictionary_size = 388
代入:2 * dictionary_size >= *new_capacity
= 776 >= 1028 , 值为false。 故不会进行转换为快数组。