5分鐘徹底理解Object.keys

為什麼Object.keys的返回值會自動排序?

例子是這樣的:

5分鐘徹底理解Object.keys

而下面這例子又不自動排序了?

5分鐘徹底理解Object.keys

答案

對於上面那個問題先給出結論,Object.keys在內部會根據屬性名key的類型進行不同的排序邏輯。分三種情況:

【1】如果屬性名的類型是Number,那麼Object.keys返回值是按照key從小到大排序

【2】如果屬性名的類型是String,那麼Object.keys返回值是按照屬性被創建的時間升序排序。

【3】如果屬性名的類型是Symbol,那麼邏輯同String相同

這就解釋了上面的問題。

下面我們詳細介紹Object.keys被調用時,背後發生了什麼。


當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)得到最終的結果


將參數轉換成Object(ToObject(0))

ToObject操作根據下表將參數O轉換為Object類型的值:

參數類型 | 結果 |
--------------|------------------------|
Undefined | 拋出TypeError |
Null | 拋出TypeError |
Boolean | 返回一個新的 Boolean 對象 |
Number | 返回一個新的 Number 對象 |
String | 返回一個新的 String 對象 |
Symbol | 返回一個新的 Symbol 對象 |
Object | 直接將Object返回 |

因為Object.keys內部有ToObject操作,所以Object.keys其實還可以接收其他類型的參數。

上表詳細描述了不同類型的參數將如何轉換成Object類型。

我們可以簡單寫幾個例子試一試:

先試試null會不會報錯:

5分鐘徹底理解Object.keys

果然報錯了。

接下來我們試試數字的效果:

5分鐘徹底理解Object.keys

返回空數組。

為什麼會返回空數組?

5分鐘徹底理解Object.keys

返回的對象沒有任何可提取的屬性,所以返回空數組也是正常的。

然後我們再試一下String的效果:

5分鐘徹底理解Object.keys

我們會發現返回了一些字符串類型的數字,這是因為String對象有可提取的屬性

5分鐘徹底理解Object.keys

因為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被調用時,執行以下步驟(其實就一步):

  1. Return ! OrdinaryOwnPropertyKeys(O).

而OrdinaryOwnPropertyKeys是這樣規定的:

1、聲明變量keys值為一個空列表(List類型)
2、把每個Number類型的屬性,按數值大小升序排序,並依次添加到keys中
3、把每個String類型的屬性,按創建時間升序排序,並依次添加到keys中
4、把每個Symbol類型的屬性,按創建時間升序排序,並依次添加到keys中
5、將keys返回(return keys)

上面這個規則不光規定了不同類型的返回順序,還規定了如果對象的屬性類型是數字,字符與Symbol混合的,那麼返回順序永遠是數字在前,然後是字符串,最後是Symbol。

舉個例子:

5分鐘徹底理解Object.keys

屬性的順序規則中雖然規定了Symbol的順序,但其實Object.keys最終會將Symbol類型的屬性過濾出去。(原因是順序規則不只是給Object.keys一個API使用,它是一個通用的規則)


將List類型轉換為Array得到最終結果(CreateArrayFromList( elements ))

現在我們已經得到了一個對象的屬性列表,最後一步是將List類型的屬性列表轉換成Array類型。

將List類型的屬性列表轉換成Array類型非常簡單:

1、先聲明一個變量array,值是一個空數組
2、循環屬性列表,將每個元素添加到array中
3、將array返回

該順序規則還適用於其他API

上面介紹的排序規則同樣適用於下列API:

Object.entries

Object.values

for...in

循環

Object.getOwnPropertyNames

Reflect.ownKeys

注意:以上API除了Reflect.ownKeys之外,其他API均會將Symbol類型的屬性過濾掉。


分享到:


相關文章: