- Published on
V8中的对象
v8对对象属性的访问
排序属性和常规属性
在添加属性后,按照ecma的规范,对象的属性值按照如下的顺序排列
- 数字属性按照顺序
- 字符串属性按照添加顺序
数字属性被称为排序属性
字符串属性被称为常规属性 在v8内部存在两个字段用于对这两种属性进行分类,其中
字符串属性被称为常规属性 在v8内部存在两个字段用于对这两种属性进行分类,其中
element
用于存储数字属性,properties
用于存储字符串属性快属性和慢属性
当
properties
的数量小于等于十个的时候,v8会对索引进行优化,直接把字符串属性放在和properties
统一层级的位置,以加快索引速度。称为对象内属性 对象内属性通过chrome观察
- 当常规属性数量 = 10时 只有一个
elements
属性。 - 当常规属性数量 = 20时 出现了
elements
属性和properties
属性,此时properties
按线性排列,并且properties
中保存的时从第11个开始的属性。 - 当常规属性数量 = 200时 出现了
elements
属性和properties
属性,此时properties
按非线性排列
线性配列的属性被称为“快属性”,非线性排列属性的称为“慢属性”
elements
和数组索引
洞和打包元素
删除元素会产生hole,在属性访问的时候如果对象本身上不存在该属性则会往原型链上查找,而通过标记一个元素的值为the_hole
可以简化查找过程,尤其是数组
快速elements和字典elements
针对属性描述符和包含大量空洞的数组使用字典的形式更加节省内存
const sparseArray = [];
sparseArray[9999] = 'foo';
const array = [];
Object.defineProperty(array, 0, {value: 'fixed' configurable: false});
console.log(array[0]); // Prints 'fixed'.
array[0] = 'other value'; // Cannot override index 0.
console.log(array[0]); //
整数和小数
对于只有整数的数组,v8会进行优化
const a1 = [1, 2, 3]; // Smi Packed
const a2 = [1, , 3]; // Smi Holey, a2[1] reads from the prototype
const b1 = [1.1, 2, 3]; // Double Packed
const b2 = [1.1, , 3]; // Double Holey, b2[1] reads from the prototype
隐藏类
JS作为动态语言,可以随时随地为一个对象增加一个属性(Map),在v8内部,为了对象的访问速度提出了隐藏类的概念。
当我们创建一个对象时,v8会为我们的对象分配一个隐藏类。隐藏类中记录了两点
- 有哪些属性
- 属性的偏移量 通过隐藏类访问对象属性时,可以先得到某个属性相对于对象起始位置的偏移量,然后通过偏移量去访问相对应的值。 上面提到的快属性就会存在于隐藏类上,而慢属性不会。 慢属性的键、值、以及属性描述符记录在
properties
中
复用隐藏类
当创建多个对象具有相同的属性名称、属性个数、以及顺序的时候,v8就会复用之前创建的隐藏类 同样的,当一个对象的属性被反复添加和删除时,隐藏类也会被重复创建和销毁
DescriptorArray
除了
Map
表示的隐藏类以外,还有两个属性共同构成了隐藏类DescriptorArray
和TransitionArray
。 这里先介绍DescriptorArray
。DescriptorArray
记录了Map
中的属性,在记录时会按照添加顺序,同时还记录了关于属性的基本信息。 以这张图为例。 由于不断添加属性(图中name
、height
等箭头上的属性)我们获得了Map0 - Map5
这五个隐藏类。同时还有三个DescriptorArray
其中Map1 - Map3
指向了同一个DescriptorArray
,而且每个Map
也记录了在DescriptorArray
中存放了多少了自己的属性。 这样在访问的时候,只需要访问自己的部分。对于Map1
而言就只需要访问DescriptorArray
中的第1个属性。
而Map4
和Map5
则共享了DescriptorArray2
TransitionArray
TransitionArray
记录了属性->隐藏类的映射关系,也就是说我有一个类A
,当我在A
上添加属性时,生成的新的隐藏类会是哪一个。 TransitionArray
用于追踪隐藏类的属性出现分叉的情况。比如在图中我们添加完heigh
t和width之后
,我们再去添加experience
和prominence
时就是产生"分叉",因为这两个属性并不会同时存在,而是会产生分支结构。 通过TransitionArray
我们可以快速定位具有不同的属性的对象所对应的隐藏类 slack tracking
上面我们说到了对象的对象内属性,当属性被存放到properties
的部分就称为backing store
。 但是实际情况不会像上面那样简单,当使用到backing store
时,10个的上限并不一定触发,这就涉及到了v8的slack tracking策略。
由于JS可以动态添加属性的策略,在为对象分配内存的时候通常会先分配一个较大的值,再根据实际的使用情况利用垃圾回收机制回收一部分空间。而追踪实际的使用情况的这部分被称为slack tracking。 当v8使用隐藏类构建对象时,会对构建的对象进行计数。
- 当一个新的隐藏类被创建的时候,它可以有10个对象内属性。当我们不断添加新的属性的之后,对象内属性可用的数量会不断减少。
- 后续通过隐藏类创建新的对象
- 创建的数量达到7个
- 对象内属性剩余可用的部分被清空,已有的对象内属性被保留,后续的新属性放入
backing store
中。 - slack tracking 完成
- 此时通过这个隐藏类再去创建对象,那么这个对象的大小将维持一个较小(相对slack tracking之前)的内存占用。
举例
> %DebugPrint(m1);
DebugPrint: 0x49fc866d: [JS_OBJECT_TYPE]
- map: 0x58647385 <Map(HOLEY_ELEMENTS)> [FastProperties]
- prototype: 0x49fc85e9 <Object map = 0x58647335>
- elements: 0x28c821a1 <FixedArray[0]> [HOLEY_ELEMENTS]
//此时两个属性
- properties: 0x28c821a1 <FixedArray[0]> {
0x28c846f9: [String] in ReadOnlySpace: #name: 0x5e412439 <String[10]: #Matterhorn> (const data field 0)
0x5e412415: [String] in OldSpace: #height: 4478 (const data field 1)
}
0x58647385: [Map]
- type: JS_OBJECT_TYPE
//大小
- instance size: 52
//对象内属性
- inobject properties: 10
- elements kind: HOLEY_ELEMENTS
//剩余的对象内属性
- unused property fields: 8
- enum length: invalid
- stable_map
- back pointer: 0x5864735d <Map(HOLEY_ELEMENTS)>
- prototype_validity cell: 0x5e4126fd <Cell value= 0>
- instance descriptors (own) #2: 0x49fc8701 <DescriptorArray[2]>
- prototype: 0x49fc85e9 <Object map = 0x58647335>
- constructor: 0x5e4125ed <JSFunction Peak (sfi = 0x5e4124dd)>
- dependent code: 0x28c8212d <Other heap object (WEAK_FIXED_ARRAY_TYPE)>
- construction counter: 6
当slack tracking 完成后
DebugPrint: 0x5cd08751: [JS_OBJECT_TYPE]
- map: 0x4b387385 <Map(HOLEY_ELEMENTS)> [FastProperties]
- prototype: 0x5cd086cd <Object map = 0x4b387335>
- elements: 0x586421a1 <FixedArray[0]> [HOLEY_ELEMENTS]
- properties: 0x586421a1 <FixedArray[0]> {
0x586446f9: [String] in ReadOnlySpace: #name:
0x51112439 <String[10]: #Matterhorn> (const data field 0)
0x51112415: [String] in OldSpace: #height:
4478 (const data field 1)
}
0x4b387385: [Map]
- type: JS_OBJECT_TYPE
- instance size: 20
//对象内属性变为2
- inobject properties: 2
- elements kind: HOLEY_ELEMENTS
//剩余对象内属性变为0
- unused property fields: 0
- enum length: invalid
- stable_map
- back pointer: 0x4b38735d <Map(HOLEY_ELEMENTS)>
- prototype_validity cell: 0x511128dd <Cell value= 0>
- instance descriptors (own) #2: 0x5cd087e5 <DescriptorArray[2]>
- prototype: 0x5cd086cd <Object map = 0x4b387335>
- constructor: 0x511127cd <JSFunction Peak (sfi = 0x511125f5)>
- dependent code: 0x5864212d <
相关资料