我们先从一个示例开始看起,如下所示
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.entriesObject.valuesfor-inObject.getOwnPropertyNamesReflect.ownKeys
不过需要注意的是,以上 API 除了 Reflect.ownKeys 之外,其他 API 均会将 Symbol 类型的属性过滤掉
如果想要保证对象遍历的输出顺序,可以将对象转换为数组来进行操作