我们首先需要了解的是,在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)了。哈哈,还是不建议这样做了。如上所述,但凡引擎修改了规则,完蛋。