JavaScript中对象属性的顺序问题

        我们首先需要了解的是,在JavaScript中,对象的属性名只能是字符串或者symbol。假设我们给将属性名设置为非string和symbol类型,都会被隐式转换成字符串。

let a = {
    1: 1,
    true: true,
}
// 等同于
let a = {
    '1': 1,
    'true': true,
}

a[{}] = {}
a[()=>{}] = 'fun'
// 等同于
a["[object Object]"] = {}
a["()=>{}"] = 'fun'

        考虑如下代码:

let a = {};
a[0] = 0;
a['b'] = 'b';
a[Symbol('symbol2')] = 'symbol1';
a[2] = 2;
a[Symbol('symbol1')] = 'symbol2';
a[{}] = '{}'
a['a'] = 'a';
a['1'] = 1;

console.log('Object.keys(a)', Object.keys(a));
console.log('Reflect.ownKeys(a)', Reflect.ownKeys(a));

题外话:

        Symbol 类型的属性在 JavaScript 中被认为是不可枚举的。因此如果简单通过Object.keys或for in循环所有key的话。是不会得到symbol类型的属性名的。但是我们可以通过Reflect.ownKeys()输出所有属性名。
        Reflect.ownKeys方法用于返回对象的所有属性,基本等同于Object.getOwnPropertyNames与Object.getOwnPropertySymbols之和。

        getOwnPropertyNames:用于返回一个由指定对象的所有自身属性(包括不可枚举属性但不包括Symbol值作为名称的属性)的属性名(包括不可枚举属性)组成的数组。这个方法在ES5中就引入了。

        getOwnPropertySymbols:这是一个ES6(ECMAScript 2015)中引入的方法。用于返回一个由指定对象的所有自身Symbol值的属性键组成的数组。这个方法不会返回字符串键或不可枚举的属性键。

上述代码输出的对象a的所有key数组。输出结果如下:

可以看到,顺序并不是完全按照插入顺序来排列的。属性直接也是有区别的。

在ES6及之后的版本中,对象的属性迭代顺序大致遵循以下规则:

        1、数值属性:首先会迭代对象的所有数值键,按照数值的升序进行。

        2、字符串属性:然后按字符串的插入顺序依次迭代字符串属性

        3、symbol属性:再然后按symbol的插入顺序依次迭代symbol属性

        4、继承属性:当一个对象上有自己的属性,并且还继承一些属性的时候。整体也是按照上述三条规则排序的,但是插入顺序可能需要注意。这里暂不考虑这种特殊复杂情况。

需要注意的是,尽管上述规则在大多数现代JavaScript引擎中得到了遵守,但ECMAScript标准并没有强制要求这样的顺序。因此,为了编写健壮的代码,最好不要依赖特定的属性迭代顺序。我们做为一个知识点了解即可。

例如:字符串和symbol的顺序可能因引擎差异会按照其UTF-16编码单元的字典顺序进行排序。


深入了解,这里引用网上一些知识点:

        在V8引擎中,对象的属性会根据其类型和使用情况进行不同的存储和处理。数字属性在V8中通常被称为“elements”,而字符串属性则被称为“properties”。

        对于迭代对象属性时的顺序,V8引擎会遵循ECMAScript规范中的规则。根据规范,可索引的属性(通常是数字键)会按照索引值大小升序排列,而命名属性(通常是字符串键)则根据创建的顺序升序排列

        在V8引擎中,数字属性(也被称为“elements”或“索引属性”)是按顺序迭代的关键在于其底层的存储机制。

        V8使用了一种优化的数据结构来存储对象的数字属性,这种结构通常被称为“元素数组”或“属性数组”。当对象主要包含连续的数字键时,V8会将这些属性存储在一个连续的内存区域中,以便高效地按索引访问和迭代。

        这种数组结构允许V8引擎直接通过索引值来快速查找和访问属性。当迭代数字属性时,V8引擎可以简单地遍历这个数组,按照索引值的升序来访问每个属性。

        这种机制使得数字属性的迭代变得非常高效,因为引擎可以直接利用数组的连续内存布局来按顺序访问每个元素,而无需进行复杂的查找或排序操作。

需要注意的是,这种优化主要适用于连续的数字键。对于非数字键或稀疏的数字键(即存在大量未定义的索引),V8可能会采用其他的数据结构或策略来存储和迭代属性,以确保性能和内存使用的平衡。

        总之,V8引擎通过利用专门的元素数组来存储和迭代数字属性,实现了按索引值升序的迭代顺序。这种机制使得在处理包含连续数字键的对象时,能够高效地按顺序访问和迭代这些属性。

        经过简单测试,在同时存储10w个属性的情况下,即使存储10w不连续的数字属性,也要比10w个字符串平均快2-3倍左右。要是存储连续的数字,则快10倍不止了

let b = {};
console.time('b1')
for (let index = 0; index < 10 * 10000; index++) {
    b[Math.floor(Math.random() * 100000)] = index;
    // b[index] = index;
}
console.timeEnd('b1')
b = {};
console.time('b2')
for (let index = 0; index < 10 * 10000; index++) {
    b[`-${index}`] = -index
}
console.timeEnd('b2')


        到这里就结束了,不过还有个骚操作值得一说

        假设有一个没有重复元素的数组需要排序的话。我们就可以遍历一遍数组,将这些数据依次添加到对象中,对象则会帮我们做好排序了,然后再输出对象的所有key。理论上是比任何排序方法都要快的。但是空间复杂度就是是O(n)了。哈哈,还是不建议这样做了。如上所述,但凡引擎修改了规则,完蛋。

  • 23
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值