为什么 Object.keys 的返回值会自动排序

为什么 Object.keys 的返回值会自动排序

我们先从一个示例开始看起,如下所示

1
2
3
4
5
6
7
var arr = ['a', 'b', 'c']
console.log(Object.keys(arr)) // ['0', '1', '2']

// -------------------------------

var an_obj = { 100: 'a', 2: 'b', 7: 'c' }
console.log(Object.keys(an_obj)) // ['2', '7', '100']

可以明显的发现,在下面一种的遍历方式当中,结果的顺序被改变了,至于为什么会发生这样的情况,我们需要来看看在 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
2
3
4
5
Object.keys(null)   // TypeError

Object.keys(true) // []

Object.keys('abc') // ['0', '1', '2']

EnumerableOwnPropertyNames(obj, key)

接下来就是获得属性列表,过程有很多,比较重要的一个是调用对象的内部方法 OwnPropertyKeys 获得对象的 ownKeys,也正是该方法决定了属性的顺序

  • 声明变量 keys 值为一个空列表(List 类型)
  • 把每个 number 类型的属性,按数值大小升序排序,并依次添加到 keys
  • 把每个 string 类型的属性,按创建时间升序排序,并依次添加到 keys
  • 把每个 Symbol 类型的属性,按创建时间升序排序,并依次添加到 keys
  • keys 返回(return keys

除此之外,还有一点需要补充,如果对象的属性类型是数字,字符与 Symbol 混合的,那么返回顺序永远是数字在前,然后是字符串,最后是 Symbol,不过还有一点需要注意,就是虽然在规范当中规定了 Symbol,但是最终会将 Symbol 类型的属性过滤出去

1
2
3
4
5
6
7
8
9
10
11
12
13
let s = Symbol()

const obj = {
[s]: 'd',
7: 'a',
a: '7',
10: 'b',
b: '10',
44: 'c',
c: '44'
}

Object.keys(obj) // ['7', '10', '44', 'a', 'b', 'c']

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 类型的属性过滤掉

如果想要保证对象遍历的输出顺序,可以将对象转换为数组来进行操作

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×