本文做者: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
類型的屬性過濾掉。