JavaScript中你所不知道的普通属性和排序属性

前言

本文章源自《JavaScript知识小册》专栏,感兴趣的话还请关注点赞收藏.

上一篇文章:《JavaScript DOM property和attribute的区别

普通属性

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

arduino 复制代码
const obj = {
    'name': 'Bruse',
    'age': 16,
}

for (const key in obj) {
    console.log(key)
}

输出顺序为

name
age

排序属性

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

arduino 复制代码
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属性名并输出

arduino 复制代码
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

代码测试

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

javascript 复制代码
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塞入更多的排序属性

javascript 复制代码
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

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

添加更多的普通属性

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

javascript 复制代码
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都有了相应存放的变量

css 复制代码
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。

相关推荐
Martin -Tang5 分钟前
vite和webpack的区别
前端·webpack·node.js·vite
迷途小码农零零发6 分钟前
解锁微前端的优秀库
前端
王解1 小时前
webpack loader全解析,从入门到精通(10)
前端·webpack·node.js
老码沉思录1 小时前
写给初学者的React Native 全栈开发实战班
javascript·react native·react.js
我不当帕鲁谁当帕鲁1 小时前
arcgis for js实现FeatureLayer图层弹窗展示所有field字段
前端·javascript·arcgis
那一抹阳光多灿烂1 小时前
工程化实战内功修炼测试题
前端·javascript
放逐者-保持本心,方可放逐2 小时前
微信小程序=》基础=》常见问题=》性能总结
前端·微信小程序·小程序·前端框架
毋若成4 小时前
前端三大组件之CSS,三大选择器,游戏网页仿写
前端·css
红中马喽4 小时前
JS学习日记(webAPI—DOM)
开发语言·前端·javascript·笔记·vscode·学习
Black蜡笔小新5 小时前
网页直播/点播播放器EasyPlayer.js播放器OffscreenCanvas这个特性是否需要特殊的环境和硬件支持
前端·javascript·html