浅析JS对象中的排序属性和常规属性、对象内属性、快属性和慢属性

一、常规属性与排序属性

function Bar() {
  this[2] = 2
  this[3] = 1
  this['b'] = 'b'
  this[1] = 1
  this['a'] = 'a'
}
const bar = new Bar()
for (key in bar) {
  console.log(key)
}
// 打印的属性顺序:123ba

  可以发现属性的打印顺序和我们赋值顺序是不一样的,之所以出现这样的结果,是因为属性和属性间也是有区别的。

  bar 拥有两个隐藏属性 elementsproperties

1、数字属性 2、3、1 会放到 elements 属性中,被称为排序属性。

  elements 属性指向一个 elements 对象,在 elements 对象中,会按照属性数字的大小(从小到大)存放排序属性 —— 以 2、3、1 的顺序输入的属性会以 1、2、3 的顺序输出。

2、字符串属性 b、a 称为常规属性,会放到 properties 属性中。

  properties 属性指向一个 properties 对象,在 properties 对象中,会按照属性创建的先后顺序保存了常规属性 —— 以 b、a 的顺序输入的属性会以 b、a 的顺序输出。

二、对象内属性

  在ECMAScript规范中定义了 「数字属性应该按照索引值⼤⼩升序排列,字符 串属性根据创建时的顺序升序排列。」在这⾥我们把对象中的数字属性称为 「排序属性」,在V8中被称为 elements,字符串属性就被称为 「常规属性」, 在V8中被称为 properties。

  在V8内部,为了有效地提升存储和访问这两种属性的性能,分别使用了两个 线性数据结构来分别保存排序属性和常规属性,具体结构如下图:

  在elements对象中,会按照顺序存放排序属性,properties属性则指向了properties对 象,在properties对象中,会按照创建时的顺序保存了常规属性。

  将不同的属性分别保存到elements属性和properties属性中,虽然简化了程序的复杂度,但是在查找元素时,却多了⼀步操作,比如执行 bar.B 这个语句来查找B的属性值,那么在V8会先查找出properties属性所指向的对象properties,然后再在properties对象中查找B属性,这种方式在查找过程中增加了一步操作, 因此会影响到元素的查找效率。

  基于这个原因,V8采取了⼀个权衡的策略以加快查找属性的效率,这个策略是将部分常规属性直接存储到对象本身我们把这称为 「对象内属性(in-object?properties)」。对象在内存中的展现形式你可以参看下图:

  采⽤对象内属性之后,常规属性就被保存到bar对象本身了,这样当再次使用bar.B来查找B的属性值时, V8就可以直接从bar对象本身去获取该值就可以了,这样减少查找属性值的步骤,增加了查找效率。

  不过对象内属性的数量是固定的,默认是10个,如果添加的属性超出了对象分配的空间,则它们将被保存在常规属性存储中。虽然属性存储多了一层间接层,但可以自由地扩容。

三、快属性与慢属性

  elements 对象和 properties 对象都是用线性结构存储属性的,但这样存在效率问题,因为在查某个属性的时候,要先去查 elements 对象或 properties 对象,增加了查找步骤。

  为了减轻这个问题,引擎会将部分常规属性(默认是 10 个)直接存到对象下面,称为对象内属性。

1、将保存在线性结构里的属性称为快属性,快属性查找快,增删慢;

2、当对象属性很多的时候,就会放大线性结构增删属性的缺点,因此引擎会利用非线性结构(字典)来保存属性,这时候被存储的属性称为慢属性。

  通常,我们将保存在线性数据结构中的属性称之为“快属性”,因为线性数据结构中只需要通过索引即可以访问到属性,虽然访问线性结构的速度快,但是如果从线性结构中添加或者删除大量的属性时,则执行效率会非常低,这主要因为会产生大量时间和内存开销。

  因此,如果一个对象的属性过多时,V8为就会采取另外一种存储策略,那就是“慢属性”策略,但慢属性的对象内部会有独立的非线性数据结构(词典)作为属性存储容器。所有的属性元信息不再是线性存储的,而是直接保存在属性字典中。

四、v8原理——对象的存储和访问

  V8 实现对象存储时,并没有完全采用字典的存储方式,这主要是出于性能的考量。 因为字典是非线性的数据结构,查询效率会低于线性的数据结构,V8 为了提升存储和查找 效率,采用了一套复杂的存储策略。

1、把对象中的数字属性称为排序属性,在 V8 中被称为 elements。 数字属性应该按照索引值大小升序排列

2、字符串属性就被称为常规属性,在 V8 中被称为 properties,字符串属性根据创建时的顺序升序排列

3、两个属性都有时,排序属性先于常规属性。

4、在 V8 内部,为了有效地提升存储和访问这两种属性的性能,分别使用了两个线性数据结构来分别保存排序属性和常规属性。

  分解成这两种线性数据结构之后,如果执行索引操作,那么 V8 会先从 elements 属性中按照顺序读取所有的元素,然后再在 properties 属性中读取所有的元素,这样就完成一次索引操作。

5、对象内属性,将部分常规属性直接存储到对象本身, 对象内属性的数量是固定的,默认是 10 个,如果添加的属性超出了对象分配的空间, 则它们将被保存在常规属性存储中。

6、如果对象中的属性过多时,或者存在反复添加或者删除属性的操作,那么 V8 就会将线性的存储模式降级为非线性的字典存储模式,这样虽然降低了查找速度,但是却提升了修改对象属性的速度。

posted @ 2017-06-14 16:06  古兰精  阅读(1114)  评论(0编辑  收藏  举报