JavaScript中普通属性和排序属性详解

 更新时间:2023年09月18日 10:14:48   作者:LBruse  
JavaScript属性是对象中的特性,用于描述对象的状态,每个JavaScript对象都有一组属性,可以通过点号(.)或方括号([])访问和操作这些属性,本文将给大家讲讲JavaScript中你所不知道的普通属性和排序属性,需要的朋友可以参考下

普通属性

JavaScript中定义对象时,普通的以字符串为键定义的属性为普通属性,特点是在遍历对象属性时根据创建时的顺序排序

const obj = {
    'name': 'Bruse',
    'age': 16,
}
for (const key in obj) {
    console.log(key)
}

输出顺序为

name
age

排序属性

排序属性则是以数字为键定义的属性,特点是按照索引值的大小进行升序排序,优先级优于普通属性。

const obj = {
    'name': 'Bruse', // 普通属性
    'age': 16, // 普通属性
    2: 'obj2' // 排序属性
}
obj[1] = 'obj1' // 排序属性
obj[99] = 'obj99' // 排序属性
obj[10] = 'obj10' // 排序属性
for (const key in obj) {
    console.log(key)
}

输出

1
2
10
99
name
age

可以看到在使用for ... in 遍历obj的属性时,排序属性遍历顺序优先于普通属性,同时排序属性也是按照键值进行升序排序的,键值越小,遍历顺序越往前

字符串数字作为键

obj中新建一个键为'3',值为112的属性,遍历obj属性名并输出

const obj = {
    'name': 'Bruse', // 普通属性
    'age': 16, // 普通属性
    2: 'obj2', // 排序属性
    '3': 112 // 转换,也是排序属性
}
obj[1] = 'obj1' // 排序属性
obj[99] = 'obj99' // 排序属性
obj[10] = 'obj10' // 排序属性
for (const key in obj) {
    console.log(key)
}

输出

1
2
3
10
99
name
age

可以看到即便在定义属性键时,是字符串类型的数字3,也会做一下转换,将其转换为排序属性

排序属性 VS 普通属性

存储方式

首先排序属性普通属性在对象中的存储方式不一样

可以简单地理解为obj对象中,分别有着elementsproperties两个内置的属性,elements可以理解为是一个数组,而数字键则是属性在该数组中的下标,也就是内存偏移量。通过下标访问数组中的某个元素是很快的。当我们要访问obj.1时,会先从obj对象中内置的elements属性中通过1这个下标进行查询,最终找到obj[1]的值为obj1

properties可以理解为是一个Map,而字符串键则是该Map中的keyvalue则是对应的属性值。当访问obj.name时,会先从elements中进行查找,结果是找不到,然后再从properties中进行查找,但从properties中查找则没有elements中查找快,因为elements可以通过计算偏移量来进行访问,但是properties要hash计算访问。

对象内属性

其实上图还并不是全貌,因为即便有着elementsproperties分别存放排序属性和普通属性,但是无论访问排序属性和普通属性[排序属性访问速度优于普通属性],都是要先访问obj这个对象的elementsproperties内置属性,然后再通过属性键访问到具体的属性值,其实就相当于obj.name = obj.properties.name,还存在优化空间,这个时候对象内属性就出场了。

对象内属性其实就是被保存到对象自身的常规属性,也就是真正意义上的让obj.name不再等于obj.properties.name,而是真正的所见即所得obj.name

代码测试

为了方便理解,把之前的代码稍作修改

class People {
   constructor() {
       this.name = 'Bruse'
       this.age = 16
       this[2] = 'obj2'
   }
}
const obj = new People()
obj[1] = 'obj1' // 排序属性
obj[99] = 'obj99' // 排序属性
obj[10] = 'obj10' // 排序属性
debugger  // 避免执行过快,导致还来不及生成内存快照,obj对象就已被回收
for (const key in obj) {
   console.log(key)
}

F12打开浏览器开发者工具,当debugger阻塞住代码往下执行时,点击Memory,生成内存快照

可以看到obj这个对象中存在内置属性elements和一些内属性name[10][99][1][2],暂时并没有内置属性properties

首先因为obj属性并不多,此时对象内属性的数量还比较少,所以此时并不需要内置属性properties来存放普通属性,而是将name等普通属性当做是对象的内置属性存放即可,访问速度比访问elementsproperties更快。

Tips:不要看到elements中10、99排在1、2前面,就以为elements不是按照数字升序排列的,因为输出之后你会发现其实还是1、2、10、99这样的顺序,至于为什么在调试工具里看起来顺序有点不太一致,我也不知道...

添加更多的排序属性

接下来给obj塞入更多的排序属性

class People {
    constructor() {
        this.name = 'Bruse'
        this.age = 16
    }
}
const obj = new People()
for (let i = 0; i < 20; i++) {
    obj[i] = `obj${i}`
}
debugger
for (const key in obj) {
    console.log(key)
}

再看看看obj的变化,首先elements属性中的元素变多了

同时也并没有出现内置属性properties

因为只是增加了更多的排序属性,并没有突破内置属性(内置常规属性)的数量限制

添加更多的普通属性

接下来再塞入更多的普通属性

class People {
    constructor() {
        this.name = 'Bruse'
        this.age = 16
        for (let i = 0; i < 20; i++) {
            this[`obj${i}`] = i
        }
    }
}
const obj = new People()
debugger
for (const key in obj) {
    console.log(key)
}

可以看到在这个时候obj的内置属性properties终于出现了

只不过貌似并不像elements那样方便预览其中的属性...后来经过一番查找和尝试,终于是找到了解决办法... 也很简单,就是生成内存快照时多做一步操作,勾上“在快照中添加数字值”

再次内存分析,好吧...因为生成20个常规变量的缘故,貌似也还没达到内置属性的限制...

将数量调整到50个,可以看到elementsproperties都有了相应存放的变量

for (let i = 0; i < 50; i++) {
    this[i] = `obj${i}`
    this[`obj${i}`] = i
}

困惑

单纯在Chrome上进行内存分析的话,貌似即便超出了内属性数量限制,那多出来的一部分普通属性,也非常直白地展示在properties之外,这个暂时不太清楚...

总结

把上述排序属性``普通属性``内属性结合到一块来看,那么JavaScript中的对象属性存放结构应该如下图所示

首先是为了提高访问速度,对象obj本身就会有一定空间存放内属性,在访问内属性时,可以直接跳过访问elementsproperties这一步,直接访问到该属性的值。

但是内属性是有一定数量限制的,所以当超出了限制后,剩下的普通属性会被存放到properties中,而properties有点像Map,在进行属性访问的时候,需要计算出键的hash值,然后才能访问到具体的属性值。

elements则是存放排序属性用的,有点像Array,数字键即数组中的下标,所有元素按数字升序进行排序,访问属性值时则是通过下标进行访问,访问速度会比properties要快一些。

propertieselements两种存储方式之间的VS,本质上就像是数据结构中的MapArray之间的VS。

以上就是JavaScript中普通属性和排序属性详解的详细内容,更多关于JavaScript属性的资料请关注脚本之家其它相关文章!

相关文章

最新评论