我们先从一个示例开始看起,如下所示
1 | var arr = ['a', 'b', 'c'] |
可以明显的发现,在下面一种的遍历方式当中,结果的顺序被改变了,至于为什么会发生这样的情况,我们需要来看看在 Object.keys()
的调用过程中到底发生了些什么,我们通过查阅 规范 可知,在 Object.keys()
的调用过程中总共有三步流程
- 调用
ToObject(O)
将结果赋值给变量obj
- 调用
EnumerableOwnPropertyNames(obj, key)
将结果赋值给变量nameList
- 调用
CreateArrayFromList(nameList)
得到最终的结果
下面我们就分别来看看这三个步骤
ToObject(O)
首先第一步是将参数转换成 Object()
,根据参数的不同结果有所不同,如下所示
参数类型 | 结果 |
---|---|
undefined |
抛出 TypeError |
null |
抛出 TypeError |
布尔值 | 返回一个新的布尔对象 |
数值 | 返回一个新的数值对象 |
字符串 | 返回一个新的字符串对象 |
Symbol |
返回一个新的 Symbol 对象 |
Object |
直接将 Object 返回 |
几个示例
1 | Object.keys(null) // TypeError |
EnumerableOwnPropertyNames(obj, key)
接下来就是获得属性列表,过程有很多,比较重要的一个是调用对象的内部方法 OwnPropertyKeys
获得对象的 ownKeys
,也正是该方法决定了属性的顺序
- 声明变量
keys
值为一个空列表(List
类型) - 把每个
number
类型的属性,按数值大小升序排序,并依次添加到keys
中 - 把每个
string
类型的属性,按创建时间升序排序,并依次添加到keys
中 - 把每个
Symbol
类型的属性,按创建时间升序排序,并依次添加到keys
中 - 将
keys
返回(return keys
)
除此之外,还有一点需要补充,如果对象的属性类型是数字,字符与 Symbol
混合的,那么返回顺序永远是数字在前,然后是字符串,最后是 Symbol
,不过还有一点需要注意,就是虽然在规范当中规定了 Symbol
,但是最终会将 Symbol
类型的属性过滤出去
1 | let s = Symbol() |
CreateArrayFromList(nameList)
最后一步就是调用 CreateArrayFromList(nameList)
来得到最终的结果,现在已经得到了一个对象的属性列表,最后一步是将 List
类型的属性列表转换成 Array
类型
- 先声明一个变量
Array
,值是一个空数组 - 循环属性列表,将每个元素添加到
Array
中 - 将
Array
返回
一些其他的 API
上面介绍的排序规则同样适用于下列 API
Object.entries
Object.values
for-in
Object.getOwnPropertyNames
Reflect.ownKeys
不过需要注意的是,以上 API
除了 Reflect.ownKeys
之外,其他 API
均会将 Symbol
类型的属性过滤掉
如果想要保证对象遍历的输出顺序,可以将对象转换为数组来进行操作