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類型的屬性過濾掉。

相關文章
相關標籤/搜索