const obj = {
1: 1,
6: 6,
3: 3,
2: 2
}
console.log('keys', Object.keys(obj))
// ['1', '2', '3', '6']
返回的key為什麼自動按照升序排序了?
const obj2 = {
a: 'a',
c: 'c',
f: 'f',
b: 'b',
}
console.log(Object.keys(obj2))
// ['a', 'c', 'f', 'b']
這裡為什麼又不自動排序了?
看到這裡是不是覺得很懵?話不多說,我們先查檔案,看看mdn上對Object.keys
的描述:
Object.keys() 方法會返回一個由一個給定物件的自身可列舉屬性組成的陣列,陣列中屬性名的排列順序和正常回圈遍歷該物件時返回的順序一致 。
emm,然而它並沒有說到底是按哪種順序返回的。
既然檔案上找不到,那我們就一步一步來慢慢研究
if (!Object.keys) {
Object.keys = (function () {
var hasOwnProperty = Object.prototype.hasOwnProperty,
hasDontEnumBug = !({toString: null}).propertyIsEnumerable('toString'),
dontEnums = [
'toString',
'toLocaleString',
'valueOf',
'hasOwnProperty',
'isPrototypeOf',
'propertyIsEnumerable',
'constructor'
],
dontEnumsLength = dontEnums.length;
return function (obj) {
if (typeof obj !== 'object' && typeof obj !== 'function' || obj === null) throw new TypeError('Object.keys called on non-object');
var result = [];
for (var prop in obj) {
if (hasOwnProperty.call(obj, prop)) result.push(prop);
}
if (hasDontEnumBug) {
for (var i=0; i < dontEnumsLength; i++) {
if (hasOwnProperty.call(obj, dontEnums[i])) result.push(dontEnums[i]);
}
}
return result;
}
})()
};
從Object.keys
的polyfill的實現,我們可以發現它內部其實是用for...in
來實現的。那我們就可以去查詢for...in
遍歷時的順序規則。然而它也並沒有介紹遍歷的順序是怎樣的,那麼我們就只能去查詢ECMAScript
的規範了。
- 呼叫
ToObject(O)
將結果賦值給變數obj
- 呼叫
EnumerableOwnPropertyNames(obj, "key")
將結果賦值給變數nameList
- 呼叫
CreateArrayFromList(nameList)
得到最終的結果
ToObject(O)
)因為Object.keys內部會呼叫ToObject(O)
方法,所以它不只是可以接受物件引數,還可以接受其它型別的引數,下面這張表就是ToObject
根據不同型別的值轉成Object的對映:
引數型別 | 結果 |
---|---|
Undefined | 丟擲TypeError |
Null | 丟擲TypeError |
Number | 返回一個新的 Number 物件 |
String | 返回一個新的 String 物件 |
Boolean | 返回一個新的 Boolean 物件 |
Symbol | 返回一個新的 Symbol 物件 |
Object | 直接將Object返回 |
我們通常給Object.keys傳的引數都會是一個物件,但我們也可以來看看其它型別值的返回值會是怎樣的?
console.log(Object.keys(123)) // []
返回的是空陣列,這是因為new Number(123)並沒有可提取的屬性
console.log(Object.keys('123')) // [ '0', '1', '2' ]
字串之所以返回的不是空陣列,是因為new String('123')有可以提取的屬性
properties
。(順序取決於這裡)物件屬性列表是通過 EnumerableOwnPropertyNames
獲取的,其中比較重要的是呼叫物件的內部方法OwnPropertyKeys
獲得物件的ownKeys
(這些內容可以在ECMAScript規範裡面找到,就不展開介紹了,我們重點看排序)
The [[OwnPropertyKeys]] internal method of an ordinary object O takes no arguments. It performs the following steps when called:
- Return ! OrdinaryOwnPropertyKeys(O).
通過上面的介紹,我們可以發現keys的排序取決於 OrdinaryOwnPropertyKeys(O)
翻譯過來就是:
Symbol
型別索引按屬性建立時間以升序的順序存入注意:屬性列表properties
為List型別(List型別是ECMAScript規範型別)
properties
轉換為Array得到最終的結果。將List型別的屬性列表轉換成Array型別非常簡單:
array
,值是一個空陣列array
中array
返回Object.keys
返回的物件屬性順序將所有合法的陣列索引按升序排序
將所有字串型別索引按屬性建立時間以升序排序
將所有 Symbol
型別索引按屬性建立時間以升序排序
合法陣列索引指的是正整數,負數或者浮點數一律當做字串處理。嚴格來說物件屬性沒有數位型別的,無論是數位還是字串,都會被當做字串來處理。
const obj = {}
obj[-1] = -1
obj[1] = 1
obj[1.1] = 1.1
obj['2'] = '2'
obj['c'] = 'c'
obj['b'] = 'b'
obj['a'] = 'a'
obj[2] = 2
obj[Symbol(1)] = Symbol(1)
obj[Symbol('a')] = Symbol('a')
obj[Symbol('b')] = Symbol('b')
obj['d'] = 'd'
console.log(Object.keys(obj))
經過上面對Object.key
特性的介紹,想必大家都不會再搞錯Object.keys
的輸出順序了吧。
答案:
[ '1', '2', '-1', '1.1', 'c', 'b', 'a', 'd' ]
看到答案很多同學是不是有很多疑問?
首先我們上面說過合法陣列索引指的是正整數,負數或者浮點數一律當做字串處理。嚴格來說物件屬性沒有數位型別的,無論是數位還是字串,都會被當做字串來處理。
所以上面只有1,'2',2
是合法陣列索引,但我們知道其實它們都會被轉成字串,所以後面的2
會將前面的'2'
覆蓋,然後它們按升序排序。然後負數與浮點數一律當做字串處理按屬性建立時間以升序排序。這樣就可以得到上面的答案了。
因為在 EnumerableOwnPropertyNames
的規範中規定了返回值只應包含字串屬性(上面說了數位其實也是字串)。
我們也可以在MDN上檢視關於 Object.getOwnPropertyNames()
的描述。
Object.getOwnPropertyNames()
方法返回一個由指定物件的所有自身屬性的屬性名(包括不可列舉屬性但不包括 Symbol 值作為名稱的屬性)組成的陣列。
所以 Symbol 屬性是不會被返回的,如果要返回 Symbol 屬性可以用 Object.getOwnPropertySymbols()。
我是南玖,我們下期見!!!
-------------------------------------------
個性簽名:智者創造機會,強者把握機會,弱者坐等機會。做一個靈魂有趣的人!
如果這篇文章有幫助到你,❤️關注+點贊❤️鼓勵一下作者,文章公眾號首發,關注 前端南玖 第一時間獲取最新的文章~
歡迎加入前端技術交流群:928029210(QQ)
掃描下方二維條碼關注公眾號,回覆進群,拉你進前端學習交流群(WX),這裡有一群志同道合的前端小夥伴,交流技術、生活、內推、面經、摸魚,這裡都有哈,快來加入我們吧~ 回覆資料,獲取前端大量精選前端電子書及學習視訊~