概述
数组是所有 JavaScript 程序员非常熟悉和经常使用的数据结构,和其他的语言类似,仅仅通过一个简单的 let foo = []
就可以创建出一个数组并使用他。但是,作为一种弱类型语言,使用过其他强类型语言的程序员也许会对 javascript 的这种写法感到非常疑惑:let array = [0, 'a', "hello"]
。WTF?发生了什么,难道数组里面的元素类型不应该是完全一致的么?这是发生了什么魔法么。
JavaScript 到底是如何构建和管理数组的呢?这篇文章就以 nodejs 依赖的 v8 引擎为例,深入理解 js 管理数组的原理。
注 如果觉得不想读完整篇文章,只想了解大概的内容,可以到文章末尾阅读 小结
章节。
注 为了文章的严谨性,本文采用的是 Nodejs
版本 16.13.0
在提交: 8fdabcb 所依赖的 V8 版本: 9.4.146
,其他的版本特性和改动均不再此次讨论的范围内。
JS 的数组定义
首先来看一下 MDN 的文档是怎么定义 JS 中的数组类型的:
The JavaScript Array class is a global object that is used in the construction of arrays; which are high-level, list-like objects.
MDN Array 定义
JS 数组的“类型”
什么?JS 的数组什么时候有了类型?没错,JS 的数组是有类型的,只不过在 JavaScript 的层次,数组的类型是没有意义的。但是在实际的 JavaScript 引擎中,引入类型是非常正常的做法,例如 V8 引擎在实现数组的是后就引入了类型的概念。我们下面来简单介绍一下,在 V8 层面上,数组的类型究竟是怎么实现的。
首先给出下图,这是 V8 定义的 JS 数组类型。
这张图中所展示的类型主要有以下几个特点:
- 所有的类型以
_ELEMENTS
结尾
- 都用下划线分割为了三段
- 第一段有两种值:
PACKED
和 HOLEY
- 第二段有三种值:
SMI
、 DOUBLE
和 ``
我们来对以上四点进行分点解析
这是 V8 中一个很有意思的地方,我把相关的源代码贴在下方:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
|
// elements-kind.h
// The "fast" kind for elements that only contain SMI values. Must be first
// to make it possible to efficiently check maps for this kind.
PACKED_SMI_ELEMENTS,
HOLEY_SMI_ELEMENTS,
// The "fast" kind for tagged values. Must be second to make it possible to
// efficiently check maps for this and the PACKED_SMI_ELEMENTS kind
// together at once.
PACKED_ELEMENTS,
HOLEY_ELEMENTS,
// The "fast" kind for unwrapped, non-tagged double values.
PACKED_DOUBLE_ELEMENTS,
HOLEY_DOUBLE_ELEMENTS,
// The nonextensible kind for elements.
PACKED_NONEXTENSIBLE_ELEMENTS,
HOLEY_NONEXTENSIBLE_ELEMENTS,
// The sealed kind for elements.
PACKED_SEALED_ELEMENTS,
HOLEY_SEALED_ELEMENTS,
// The frozen kind for elements.
PACKED_FROZEN_ELEMENTS,
HOLEY_FROZEN_ELEMENTS,
// The "slow" kind.
DICTIONARY_ELEMENTS,
// Elements kind of the "arguments" object (only in sloppy mode).
FAST_SLOPPY_ARGUMENTS_ELEMENTS,
SLOW_SLOPPY_ARGUMENTS_ELEMENTS,
// For string wrapper objects ("new String('...')"), the string's characters
// are overlaid onto a regular elements backing store.
FAST_STRING_WRAPPER_ELEMENTS,
SLOW_STRING_WRAPPER_ELEMENTS,
// Fixed typed arrays.
#define TYPED_ARRAY_ELEMENTS_KIND(Type, type, TYPE, ctype) TYPE##_ELEMENTS,
TYPED_ARRAYS(TYPED_ARRAY_ELEMENTS_KIND)
RAB_GSAB_TYPED_ARRAYS(TYPED_ARRAY_ELEMENTS_KIND)
#undef TYPED_ARRAY_ELEMENTS_KIND
// WasmObject elements kind. The actual elements type is read from the
// respective WasmTypeInfo.
WASM_ARRAY_ELEMENTS,
// Sentinel ElementsKind for objects with no elements.
NO_ELEMENTS,
// Derived constants from ElementsKind.
FIRST_ELEMENTS_KIND = PACKED_SMI_ELEMENTS,
LAST_ELEMENTS_KIND = RAB_GSAB_BIGINT64_ELEMENTS,
FIRST_FAST_ELEMENTS_KIND = PACKED_SMI_ELEMENTS,
LAST_FAST_ELEMENTS_KIND = HOLEY_DOUBLE_ELEMENTS,
FIRST_FIXED_TYPED_ARRAY_ELEMENTS_KIND = UINT8_ELEMENTS,
LAST_FIXED_TYPED_ARRAY_ELEMENTS_KIND = BIGINT64_ELEMENTS,
FIRST_RAB_GSAB_FIXED_TYPED_ARRAY_ELEMENTS_KIND = RAB_GSAB_UINT8_ELEMENTS,
LAST_RAB_GSAB_FIXED_TYPED_ARRAY_ELEMENTS_KIND = RAB_GSAB_BIGINT64_ELEMENTS,
TERMINAL_FAST_ELEMENTS_KIND = HOLEY_ELEMENTS,
FIRST_ANY_NONEXTENSIBLE_ELEMENTS_KIND = PACKED_NONEXTENSIBLE_ELEMENTS,
LAST_ANY_NONEXTENSIBLE_ELEMENTS_KIND = HOLEY_FROZEN_ELEMENTS,
// Alias for kSystemPointerSize-sized elements
#ifdef V8_COMPRESS_POINTERS
SYSTEM_POINTER_ELEMENTS = PACKED_DOUBLE_ELEMENTS,
#else
SYSTEM_POINTER_ELEMENTS = PACKED_ELEMENTS,
#endif
|
可以看到,v8 在这个版本中定义了 41 种 elements
类型。这些 elements
究竟起到什么作用?这个在 谷歌的这篇 blog 中有讲到:
单纯从 JavaScript 的角度看,对象实际上就是就是以 string 作为 key
,任意值作为 value
的字典。当然在遍历的时候,情况会有差别。不过不同类型(string, number, symbol)的属性(key),的行为基本上是一致的(从字典的使用方式上来说)。但是出于性能和内存占用的考虑,V8 设计了不一样的属性类型。
其中,最显著的差别就在于,普通对象属性
和 数组下标
了。
举一个简单的例子:
1
2
3
|
const object = {first: "1", second: "2"};
const arr = ["1", "2"];
|
其中,object.first 和 object.second 是对象的属性(named property),而 1
/2
属于是数组 arr
的元素(element),他们之间存在巨大差别。如图所示:
element 和 property 甚至不是使用一种数据结构进行存储。在 V8 的眼里,element 和 元素的属性是完全不同的两种东西。
下划线分割的三段说明了这些类型的三种基本层次。上文介绍的 element 属于是第一种层次,说明这些数据是元素类型
。其他的两种见下文。
PACKED
和 HOLEY
这两种值,说明了数组当前是否是稀疏的(HOLEY)
。
对于 V8 来说,这是两种存在重要差别的类型,一旦变为 HOLEY
意味着数组在之前的访问中发生了越界,数组中"有洞"了。这对于性能优化来说是一种负担。要知道在其他的语言中,数组越界是非常严重的错误,但是 js 不一样,js 认为数组越界访问不算什么大事,但是从 V8 的角度来看,数组越界如果是可以接受的,那么这种行为实际上根本没法控制,为了性能的考虑,只能用特殊的值来填补由于数组越界访问带来的“漏洞”。
例如:
1
2
|
const foo = [];
const foo[999] = 1;
|
这样的代码会带来什么后果? 如果 V8 老老实实,把 foo[999] 之前的 999 个空都分配好内存,这会带来什么样的后果?甚至更极端一些,如果不是 999
而是 99999999
呢?
所以出于性能上的考虑,HOLEY 的数组实际上处理起来要比 PACKED
的数组要多了判断条件,甚至可能触发原型链的查找。这会使得我们的数组操作变得极为缓慢。
- 第二段有三种值:
SMI
、 DOUBLE
和 ``
这三种值实际上应该被称为: smi
double
regular
,在 V8 中, smi 代表了小的整数,double 代表了浮点数和无法用 smi 代表的大整数,而 regular 代表的是普通元素,他们不能用 smi 或者 double 概括。从 smi -> double -> regular 属于是递进的关系,从 smi 到 regular,元素的类型变得越来越通用,而更通用
类型的数组,是无法重新降级,变为更精确
类型数组的。
来完成几个实验
说了那么多关于类型的东西,我们可以动动手在 Nodejs 交互式解释器 中中几个实验,看看数组在 V8 里面是怎么变化的。
- 给 node 传递一个 V8 引擎的参数:
--allow-natives-syntax
,这样可以让我们直接使用到 V8 引擎提供的底层 api。
注 这么做主要是为了探究 V8 的部分,如果我们不传递这个 flag,那么像 %CollectGarbage()
这样的,以 %
开头的 V8 api 将无法调用,因为 js 不允许变量名以 %开头。
1
2
3
4
|
[picher@picherspc ~]$ node --allow-natives-syntax
Welcome to Node.js v16.13.0.
Type ".help" for more information.
>
|
- 操作数组,观察数组类型变化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
> arr.push(1)
1
> %DebugPrint(arr)
DebugPrint: 0x2fd1f4ffda51: [JSArray]
- map: 0x002db83835f1 <Map(PACKED_SMI_ELEMENTS)> [FastProperties]
- prototype: 0x2a320a845af9 <JSArray[0]>
- elements: 0x0ec65dd20841 <FixedArray[17]> [PACKED_SMI_ELEMENTS]
- length: 1
- properties: 0x12ae6c281309 <FixedArray[0]>
- All own properties (excluding elements): {
0x12ae6c284d41: [String] in ReadOnlySpace: #length: 0x202b943c1189 <AccessorInfo> (const accessor descriptor), location: descriptor
}
- elements: 0x0ec65dd20841 <FixedArray[17]> {
0: 1
1-16: 0x12ae6c281669 <the_hole>
}
0x2db83835f1: [Map]
- type: JS_ARRAY_TYPE
- instance size: 32
- inobject properties: 0
- elements kind: PACKED_SMI_ELEMENTS
- unused property fields: 0
- enum length: invalid
- back pointer: 0x12ae6c281599 <undefined>
- prototype_validity cell: 0x202b943c15e9 <Cell value= 1>
- instance descriptors #1: 0x092220768279 <DescriptorArray[1]>
- transitions #2: 0x0172c20afc81 <TransitionArray[8]>Transition array #2:
0x172c20acd49: [String] in OldSpace: #level: (transition to (const data field, attrs: [WEC]) @ Any) -> 0x2fe087e07851 <Map(PACKED_SMI_ELEMENTS)>
0x12ae6c285949 <Symbol: (elements_transition_symbol)>: (transition to HOLEY_SMI_ELEMENTS) -> 0x002db83835a9 <Map(HOLEY_SMI_ELEMENTS)>
- prototype: 0x2a320a845af9 <JSArray[0]>
- constructor: 0x3c9fb29cdba9 <JSFunction Array (sfi = 0x3299fbc5ec79)>
- dependent code: 0x12ae6c281239 <Other heap object (WEAK_FIXED_ARRAY_TYPE)>
- construction counter: 0
[ 1 ]
|
我们主要看后半段
的详细信息,输出显示元素的类型是: PACKED_SMI_ELEMENTS
:
1
|
- elements kind: PACKED_SMI_ELEMENTS
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
> arr.push(10.24)
2
> %DebugPrint(arr)
DebugPrint: 0x3cdbba546781: [JSArray] in OldSpace
- map: 0x002db8383561 <Map(PACKED_DOUBLE_ELEMENTS)> [FastProperties]
- prototype: 0x2a320a845af9 <JSArray[0]>
- elements: 0x1e684e5693f9 <FixedDoubleArray[17]> [PACKED_DOUBLE_ELEMENTS]
- length: 2
- properties: 0x12ae6c281309 <FixedArray[0]>
- All own properties (excluding elements): {
0x12ae6c284d41: [String] in ReadOnlySpace: #length: 0x202b943c1189 <AccessorInfo> (const accessor descriptor), location: descriptor
}
- elements: 0x1e684e5693f9 <FixedDoubleArray[17]> {
0: 1
1: 10.24
2-16: <the_hole>
}
0x2db8383561: [Map]
- type: JS_ARRAY_TYPE
- instance size: 32
- inobject properties: 0
- elements kind: PACKED_DOUBLE_ELEMENTS
- unused property fields: 0
- enum length: invalid
- back pointer: 0x002db83835a9 <Map(HOLEY_SMI_ELEMENTS)>
- prototype_validity cell: 0x202b943c15e9 <Cell value= 1>
- instance descriptors #1: 0x3cdbba546899 <DescriptorArray[2]>
- transitions #1: 0x2a320a8461f9 <TransitionArray[4]>Transition array #1:
0x12ae6c285949 <Symbol: (elements_transition_symbol)>: (transition to HOLEY_DOUBLE_ELEMENTS) -> 0x002db8383519 <Map(HOLEY_DOUBLE_ELEMENTS)>
- prototype: 0x2a320a845af9 <JSArray[0]>
- constructor: 0x3c9fb29cdba9 <JSFunction Array (sfi = 0x3299fbc5ec79)>
- dependent code: 0x12ae6c281239 <Other heap object (WEAK_FIXED_ARRAY_TYPE)>
- construction counter: 0
[ 1, 10.24 ]
|
此时显示数组的元素类型是: PACKED_DOUBLE_ELEMENTS
:
1
|
- elements kind: PACKED_DOUBLE_ELEMENTS
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
> arr.push('foo')
3
> %DebugPrint(arr)
DebugPrint: 0x3cdbba546781: [JSArray] in OldSpace
- map: 0x002db8383441 <Map(PACKED_ELEMENTS)> [FastProperties]
- prototype: 0x2a320a845af9 <JSArray[0]>
- elements: 0x0ec65dd1d8c9 <FixedArray[17]> [PACKED_ELEMENTS]
- length: 3
- properties: 0x12ae6c281309 <FixedArray[0]>
- All own properties (excluding elements): {
0x12ae6c284d41: [String] in ReadOnlySpace: #length: 0x202b943c1189 <AccessorInfo> (const accessor descriptor), location: descriptor
}
- elements: 0x0ec65dd1d8c9 <FixedArray[17]> {
0: 0x0ec65dd1d971 <HeapNumber 1.0>
1: 0x0ec65dd1d961 <HeapNumber 10.24>
2: 0x21ef98e561f1 <String[3]: #foo>
3-16: 0x12ae6c281669 <the_hole>
}
0x2db8383441: [Map]
- type: JS_ARRAY_TYPE
- instance size: 32
- inobject properties: 0
- elements kind: PACKED_ELEMENTS
- unused property fields: 0
- enum length: invalid
- back pointer: 0x002db8383519 <Map(HOLEY_DOUBLE_ELEMENTS)>
- prototype_validity cell: 0x202b943c15e9 <Cell value= 1>
- instance descriptors #1: 0x3cdbba546899 <DescriptorArray[2]>
- transitions #4: 0x10313dc14e11 <TransitionArray[12]>Transition array #4:
0x3cdbba5554d9: [String] in OldSpace: #level: (transition to (const data field, attrs: [WEC]) @ Any) -> 0x2fe087e0b9d9 <Map(PACKED_ELEMENTS)>
0x12ae6c285949 <Symbol: (elements_transition_symbol)>: (transition to HOLEY_ELEMENTS) -> 0x002db8383639 <Map(HOLEY_ELEMENTS)>
0x12ae6c285241: [String] in ReadOnlySpace: #raw: (transition to (const data field, attrs: [___]) @ Any) -> 0x002db83bac29 <Map(PACKED_ELEMENTS)>
0x12ae6c2859a9 <Symbol: (frozen_symbol)>: (transition to frozen) -> 0x002db8383681 <Map(PACKED_FROZEN_ELEMENTS)>
- prototype: 0x2a320a845af9 <JSArray[0]>
- constructor: 0x3c9fb29cdba9 <JSFunction Array (sfi = 0x3299fbc5ec79)>
- dependent code: 0x12ae6c281239 <Other heap object (WEAK_FIXED_ARRAY_TYPE)>
- construction counter: 0
[ 1, 10.24, 'foo' ]
|
此时显示数组的元素类型是: PACKED_ELEMENTS
:
1
|
- elements kind: PACKED_ELEMENTS
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
> arr[9] = 'bar'
'bar'
> %DebugPrint(arr)
DebugPrint: 0x3cdbba546781: [JSArray] in OldSpace
- map: 0x002db8383639 <Map(HOLEY_ELEMENTS)> [FastProperties]
- prototype: 0x2a320a845af9 <JSArray[0]>
- elements: 0x3bb3582411e1 <FixedArray[17]> [HOLEY_ELEMENTS]
- length: 10
- properties: 0x12ae6c281309 <FixedArray[0]>
- All own properties (excluding elements): {
0x12ae6c284d41: [String] in ReadOnlySpace: #length: 0x202b943c1189 <AccessorInfo> (const accessor descriptor), location: descriptor
}
- elements: 0x3bb3582411e1 <FixedArray[17]> {
0: 0x3bb3582412a9 <HeapNumber 1.0>
1: 0x3bb3582412b9 <HeapNumber 10.24>
2: 0x21ef98e561f1 <String[3]: #foo>
3-8: 0x12ae6c281669 <the_hole>
9: 0x21ef98e70fe1 <String[3]: #bar>
10-16: 0x12ae6c281669 <the_hole>
}
0x2db8383639: [Map]
- type: JS_ARRAY_TYPE
- instance size: 32
- inobject properties: 0
- elements kind: HOLEY_ELEMENTS
- unused property fields: 0
- enum length: invalid
- stable_map
- back pointer: 0x002db8383441 <Map(PACKED_ELEMENTS)>
- prototype_validity cell: 0x202b943c15e9 <Cell value= 1>
- instance descriptors (own) #1: 0x092220768279 <DescriptorArray[1]>
- prototype: 0x2a320a845af9 <JSArray[0]>
- constructor: 0x3c9fb29cdba9 <JSFunction Array (sfi = 0x3299fbc5ec79)>
- dependent code: 0x12ae6c281239 <Other heap object (WEAK_FIXED_ARRAY_TYPE)>
- construction counter: 0
[ 1, 10.24, 'foo', <6 empty items>, 'bar' ]
|
我们看到,界外访问之后,数组的元素类型变成了: HOLEY_ELEMENTS
:
1
|
- elements kind: HOLEY_ELEMENTS
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
|
> arr[32 << 20] = 0;
0
> %DebugPrint(arr)
DebugPrint: 0x3cdbba546781: [JSArray] in OldSpace
- map: 0x002db8381869 <Map(DICTIONARY_ELEMENTS)> [FastProperties]
- prototype: 0x2a320a845af9 <JSArray[0]>
- elements: 0x3e5557730419 <NumberDictionary[52]> [DICTIONARY_ELEMENTS]
- length: 33554433
- properties: 0x12ae6c281309 <FixedArray[0]>
- All own properties (excluding elements): {
0x12ae6c284d41: [String] in ReadOnlySpace: #length: 0x202b943c1189 <AccessorInfo> (const accessor descriptor), location: descriptor
}
- elements: 0x3e5557730419 <NumberDictionary[52]> {
- max_number_key: 33554432
33554432: 0 (data, dict_index: 0, attrs: [WEC])
2: 0x21ef98e561f1 <String[3]: #foo> (data, dict_index: 0, attrs: [WEC])
9: 0x21ef98e70fe1 <String[3]: #bar> (data, dict_index: 0, attrs: [WEC])
0: 0x3bb3582412a9 <HeapNumber 1.0> (data, dict_index: 0, attrs: [WEC])
1: 0x3bb3582412b9 <HeapNumber 10.24> (data, dict_index: 0, attrs: [WEC])
20: 0x21ef98e76fa9 <String[4]: #bar1> (data, dict_index: 0, attrs: [WEC])
}
0x2db8381869: [Map]
- type: JS_ARRAY_TYPE
- instance size: 32
- inobject properties: 0
- elements kind: DICTIONARY_ELEMENTS
- unused property fields: 0
- enum length: invalid
- stable_map
- back pointer: 0x002db8383639 <Map(HOLEY_ELEMENTS)>
- prototype_validity cell: 0x202b943c15e9 <Cell value= 1>
- instance descriptors (own) #1: 0x092220768279 <DescriptorArray[1]>
- prototype: 0x2a320a845af9 <JSArray[0]>
- constructor: 0x3c9fb29cdba9 <JSFunction Array (sfi = 0x3299fbc5ec79)>
- dependent code: 0x12ae6c281239 <Other heap object (WEAK_FIXED_ARRAY_TYPE)>
- construction counter: 0
[
1,
10.24,
'foo',
<6 empty items>,
'bar',
<10 empty items>,
'bar1',
<33554411 empty items>,
0
]
|
在试图插入一个超出数组边界非常多的元素时,数组的元素类型变成了: DICTIONARY_ELEMENTS
:
1
|
- elements kind: DICTIONARY_ELEMENTS
|
经过以上几个实验,我们观察到,在 V8 的实现中,数组的类型,发生了质的改变,这些信息在 JavaScript 的层面上是完全透明的,V8 隐藏了这些细节。
v8 数组的核心源码
上文我们通过几步实验,验证了 V8 层面的数组类型变化。我们接下来正式进入主题,看看源码长什么样子:)
经过上文分析可以得出的结论是: V8 会在新建和增加数组元素时进行数组元素类型的转换。下面我们就以增加数组元素为例,阅读一下 V8 的核心源码部分:
增加数组元素
V8 关于数组元素操作的代码主要封装在 JSArray
这个类里面。在 js-array.h
源代码的注释里说明了 js 数组的两种实现:
1
2
3
4
5
6
|
// The JSArray describes JavaScript Arrays
// Such an array can be in one of two modes:
// - fast, backing storage is a FixedArray and length <= elements.length();
// Please note: push and pop can be used to grow and shrink the array.
// - slow, backing storage is a HashTable with numbers as keys.
class JSArray : public JSObject {
|
JSArray 实际上是 JSObject 的派生类,因此我们继续看 JSObject
的实现。
在 JSObject 的头文件
中可以找到添加元素的函数签名:
1
2
3
|
V8_EXPORT_PRIVATE static Maybe<bool> AddDataElement(
Handle<JSObject> receiver, uint32_t index, Handle<Object> value,
PropertyAttributes attributes);
|
我们知道
我们来看看函数的定义部分:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
|
Maybe<bool> JSObject::AddDataElement(Handle<JSObject> object, uint32_t index,
Handle<Object> value,
PropertyAttributes attributes) {
Isolate* isolate = object->GetIsolate();
DCHECK(object->map(isolate).is_extensible());
uint32_t old_length = 0;
uint32_t new_capacity = 0;
if (object->IsJSArray(isolate)) {
CHECK(JSArray::cast(*object).length().ToArrayLength(&old_length));
}
ElementsKind kind = object->GetElementsKind(isolate);
FixedArrayBase elements = object->elements(isolate);
ElementsKind dictionary_kind = DICTIONARY_ELEMENTS;
if (IsSloppyArgumentsElementsKind(kind)) {
elements = SloppyArgumentsElements::cast(elements).arguments(isolate);
dictionary_kind = SLOW_SLOPPY_ARGUMENTS_ELEMENTS;
} else if (IsStringWrapperElementsKind(kind)) {
dictionary_kind = SLOW_STRING_WRAPPER_ELEMENTS;
}
if (attributes != NONE) {
kind = dictionary_kind;
} else if (elements.IsNumberDictionary(isolate)) {
kind = ShouldConvertToFastElements(
*object, NumberDictionary::cast(elements), index, &new_capacity)
? BestFittingFastElementsKind(*object)
: dictionary_kind;
} else if (ShouldConvertToSlowElements(
*object, static_cast<uint32_t>(elements.length()), index,
&new_capacity)) {
kind = dictionary_kind;
}
ElementsKind to = value->OptimalElementsKind(isolate);
if (IsHoleyElementsKind(kind) || !object->IsJSArray(isolate) ||
index > old_length) {
to = GetHoleyElementsKind(to);
kind = GetHoleyElementsKind(kind);
}
to = GetMoreGeneralElementsKind(kind, to);
ElementsAccessor* accessor = ElementsAccessor::ForKind(to);
MAYBE_RETURN(accessor->Add(object, index, value, attributes, new_capacity),
Nothing<bool>());
if (object->IsJSArray(isolate) && index >= old_length) {
Handle<Object> new_length =
isolate->factory()->NewNumberFromUint(index + 1);
JSArray::cast(*object).set_length(*new_length);
}
return Just(true);
}
|
可以看到,代码主要分为了以下步骤:
- 检查是否是可添加元素的数组
- 检查是否是数组,如果是则获取当前数组长度
- 获取当前数组的元素类型
- 预先准备好可以转换到的字典元素类型
- 根据条件检查正确且最优的元素转换类型,并赋值给
kind
- 获取即将添加的值
value
的最优的元素类型并赋值给 to
- 如果发现数组可能会便稀疏,则
to
和 kind
会被重新赋予相应的 HOLEY
类型
- 从
kind
和 to
中选择最宽泛的类型,并赋值给 to
- 获取一个类型为
to
的 accessor
- 尝试添加元素
- 如果扩容后仍然是数组,且数组大小变大,数组重新设置长度值
小结
- JS 数组在 V8 中使用特殊的数据结构
element
保存。
- 数组大小在一定范围内: ~268MB 时,会使用 FixedArray 存放数据,并且会非常高效(相对 JS 的字典类型而言)。一旦数组可能变得非常大,V8 会转而使用字典类型存放数组数据。
- 数组的扩容,在一定范围内是自动发生的。
- JS 的数组具有类型,每一个数组都会有一个这样的类型。
- JS 的数组类型会在插入不同数据、不同操作的时候发生转变。
- 尽量使用更精确的 JS 数据类型,这样会使得 V8 更加高效。
文献引用