5分钟完全理解Object.keys

本文做者:Berwin,W3C性能工做组成员,360导航高级前端工程师。Vue.js早期用户,《深刻浅出Vue.js》(正在出版)做者。博客连接javascript

前几天一个朋友问了我一个问题:为何Object.keys的返回值会自动排序?前端

例子是这样的:java

const obj = {
  100: '一百',
  2: '二',
  7: '七'
}
Object.keys(obj) // ["2", "7", "100"]
复制代码

而下面这例子又不自动排序了?git

const obj = {
  c: 'c',
  a: 'a',
  b: 'b'
}
Object.keys(obj) // ["c", "a", "b"]
复制代码

当朋友问我这个问题时,一时间我也回答不出个因此然。故此去查了查ECMA262规范,再加上后来看了看这方面的文章,明白了为何会发生这么诡异的事情。github

故此写下这篇文章详细介绍,当Object.keys被调用时内部都发生了什么。数组

1. 答案

对于上面那个问题先给出结论,Object.keys在内部会根据属性名key的类型进行不一样的排序逻辑。分三种状况:markdown

  1. 若是属性名的类型是Number,那么Object.keys返回值是按照key从小到大排序
  2. 若是属性名的类型是String,那么Object.keys返回值是按照属性被建立的时间升序排序。
  3. 若是属性名的类型是Symbol,那么逻辑同String相同

这就解释了上面的问题。前端工程师

下面咱们详细介绍Object.keys被调用时,背后发生了什么。ecmascript

2. 当Object.keys被调用时背后发生了什么

Object.keys函数使用参数O调用时,会执行如下步骤:函数

第一步:将参数转换成Object类型的对象。

第二步:经过转换后的对象得到属性列表properties

注意:属性列表properties为List类型(List类型ECMAScript规范类型

第三步:将List类型的属性列表properties转换为Array获得最终的结果。

规范中是这样定义的:

  1. 调用ToObject(O)将结果赋值给变量obj
  2. 调用EnumerableOwnPropertyNames(obj, "key")将结果赋值给变量nameList
  3. 调用CreateArrayFromList(nameList)获得最终的结果

2.1 将参数转换成Object(ToObject(O)

ToObject操做根据下表将参数O转换为Object类型的值:

参数类型 结果
Undefined 抛出TypeError
Null 抛出TypeError
Boolean 返回一个新的 Boolean 对象
Number 返回一个新的 Number 对象
String 返回一个新的 String 对象
Symbol 返回一个新的 Symbol 对象
Object 直接将Object返回

由于Object.keys内部有ToObject操做,因此Object.keys其实还能够接收其余类型的参数。

上表详细描述了不一样类型的参数将如何转换成Object类型。

咱们能够简单写几个例子试一试:

先试试null会不会报错:

Object.keys(null)图1 Object.keys(null)

如图1所示,果真报错了。

接下来咱们试试数字的效果:

Object.keys(123)图2 Object.keys(123)

如图2所示,返回空数组。

为何会返回空数组?请看图3:

new Number(123)图3 new Number(123)

如图3所示,返回的对象没有任何可提取的属性,因此返回空数组也是正常的。

而后咱们再试一下String的效果:

Object.keys('我是Berwin')图4 Object.keys('我是Berwin')

图4咱们会发现返回了一些字符串类型的数字,这是由于String对象有可提取的属性,看如图5:

new String('我是Berwin')图5 new String('我是Berwin')

由于String对象有可提取的属性,因此将String对象的属性名都提取出来变成了列表返回出去了。

2.2 得到属性列表(EnumerableOwnPropertyNames(obj, "key")

获取属性列表的过程有不少细节,其中比较重要的是调用对象的内部方法OwnPropertyKeys得到对象的ownKeys

注意:这时的ownKeys类型是List类型,只用于内部实现

而后声明变量properties,类型也是List类型,并循环ownKeys将每一个元素添加到properties列表中。

最终将properties返回。

您可能会感受到奇怪,ownKeys已是结果了为何还要循环一遍将列表中的元素放到properties中。

这是由于EnumerableOwnPropertyNames操做不仅是给Object.keys这一个API用,它内部还有一些其余操做,只是Object.keys这个API没有使用到,因此看起来这一步不少余。

因此针对Object.keys这个API来讲,获取属性列表中最重要的是调用了内部方法OwnPropertyKeys获得ownKeys

其实也正是内部方法OwnPropertyKeys决定了属性的顺序。

关于OwnPropertyKeys方法ECMA-262中是这样描述的:

O的内部方法OwnPropertyKeys被调用时,执行如下步骤(其实就一步):

  1. Return ! OrdinaryOwnPropertyKeys(O).

OrdinaryOwnPropertyKeys是这样规定的:

  1. 声明变量keys值为一个空列表(List类型)
  2. 把每一个Number类型的属性,按数值大小升序排序,并依次添加到keys
  3. 把每一个String类型的属性,按建立时间升序排序,并依次添加到keys
  4. 把每一个Symbol类型的属性,按建立时间升序排序,并依次添加到keys
  5. keys返回(return keys

上面这个规则不光规定了不一样类型的返回顺序,还规定了若是对象的属性类型是数字,字符与Symbol混合的,那么返回顺序永远是数字在前,而后是字符串,最后是Symbol。

举个例子:

Object.keys({
  5: '5',
  a: 'a',
  1: '1',
  c: 'c',
  3: '3',
  b: 'b'
})
// ["1", "3", "5", "a", "c", "b"]
复制代码

属性的顺序规则中虽然规定了Symbol的顺序,但其实Object.keys最终会将Symbol类型的属性过滤出去。(缘由是顺序规则不仅是给Object.keys一个API使用,它是一个通用的规则)

2.3 将List类型转换为Array获得最终结果(CreateArrayFromList( elements )

如今咱们已经获得了一个对象的属性列表,最后一步是将List类型的属性列表转换成Array类型。

将List类型的属性列表转换成Array类型很是简单:

  1. 先声明一个变量array,值是一个空数组
  2. 循环属性列表,将每一个元素添加到array
  3. array返回

3. 该顺序规则还适用于其余API

上面介绍的排序规则一样适用于下列API:

  1. Object.entries
  2. Object.values
  3. for...in循环
  4. Object.getOwnPropertyNames
  5. Reflect.ownKeys

注意:以上API除了Reflect.ownKeys以外,其余API均会将Symbol类型的属性过滤掉。

相关文章
相关标签/搜索