本文做者: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
被调用时内部都发生了什么。数组
对于上面那个问题先给出结论,Object.keys
在内部会根据属性名key
的类型进行不一样的排序逻辑。分三种状况:markdown
Number
,那么Object.keys
返回值是按照key
从小到大排序String
,那么Object.keys
返回值是按照属性被建立的时间升序排序。Symbol
,那么逻辑同String
相同这就解释了上面的问题。前端工程师
下面咱们详细介绍Object.keys
被调用时,背后发生了什么。ecmascript
Object.keys
被调用时背后发生了什么当Object.keys
函数使用参数O
调用时,会执行如下步骤:函数
第一步:将参数转换成Object
类型的对象。
第二步:经过转换后的对象得到属性列表properties
。
注意:属性列表
properties
为List类型(List类型是ECMAScript规范类型)
第三步:将List类型的属性列表properties
转换为Array获得最终的结果。
规范中是这样定义的:
- 调用
ToObject(O)
将结果赋值给变量obj
- 调用
EnumerableOwnPropertyNames(obj, "key")
将结果赋值给变量nameList
- 调用
CreateArrayFromList(nameList)
获得最终的结果
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
会不会报错:
图1
Object.keys(null)
如图1所示,果真报错了。
接下来咱们试试数字的效果:
图2
Object.keys(123)
如图2所示,返回空数组。
为何会返回空数组?请看图3:
图3
new Number(123)
如图3所示,返回的对象没有任何可提取的属性,因此返回空数组也是正常的。
而后咱们再试一下String的效果:
图4
Object.keys('我是Berwin')
图4咱们会发现返回了一些字符串类型的数字,这是由于String对象有可提取的属性,看如图5:
图5
new String('我是Berwin')
由于String对象有可提取的属性,因此将String对象的属性名都提取出来变成了列表返回出去了。
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
被调用时,执行如下步骤(其实就一步):
Return ! OrdinaryOwnPropertyKeys(O).
而OrdinaryOwnPropertyKeys
是这样规定的:
keys
值为一个空列表(List类型)keys
中keys
中keys
中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使用,它是一个通用的规则)
CreateArrayFromList( elements )
)如今咱们已经获得了一个对象的属性列表,最后一步是将List类型的属性列表转换成Array类型。
将List类型的属性列表转换成Array类型很是简单:
array
,值是一个空数组array
中array
返回上面介绍的排序规则一样适用于下列API:
Object.entries
Object.values
for...in
循环Object.getOwnPropertyNames
Reflect.ownKeys
注意:以上API除了
Reflect.ownKeys
以外,其余API均会将Symbol
类型的属性过滤掉。