Object.keys?詭異特性示例詳解
先從‘詭異’的問題入手
- 例1: 純Number類型的屬性
const obj = {
1: 1,
6: 6,
3: 3,
2: 2
}
console.log('keys', Object.keys(obj))
// ['1', '2', '3', '6']
返回的key為什么自動按照升序排序了?
- 例2: 純String類型的屬性
const obj2 = {
a: 'a',
c: 'c',
f: 'f',
b: 'b',
}
console.log(Object.keys(obj2))
// ['a', 'c', 'f', 'b']
這里為什么又不自動排序了?
看到這里是不是覺得很懵?話不多說,我們先查文檔,看看mdn上對Object.keys的描述:
Object.keys() 方法會返回一個由一個給定對象的自身可枚舉屬性組成的數(shù)組,數(shù)組中屬性名的排列順序和正常循環(huán)遍歷該對象時返回的順序一致 。
emm,然而它并沒有說到底是按哪種順序返回的。
探索
既然文檔上找不到,那我們就一步一步來慢慢研究
Object.keys的polyfill的實現(xiàn)
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的實現(xiàn),我們可以發(fā)現(xiàn)它內(nèi)部其實是用for...in來實現(xiàn)的。那我們就可以去查找for...in遍歷時的順序規(guī)則。然而它也并沒有介紹遍歷的順序是怎樣的,那么我們就只能去查找ECMAScript的規(guī)范了。
Object.keys的規(guī)范定義
調(diào)用ToObject(O)將結(jié)果賦值給變量obj
調(diào)用EnumerableOwnPropertyNames(obj, "key")將結(jié)果賦值給變量nameList
調(diào)用CreateArrayFromList(nameList)得到最終的結(jié)果
第一步:將參數(shù)轉(zhuǎn)換成Object(ToObject(O))
因為Object.keys內(nèi)部會調(diào)用ToObject(O)方法,所以它不只是可以接受對象參數(shù),還可以接受其它類型的參數(shù),下面這張表就是ToObject根據(jù)不同類型的值轉(zhuǎn)成Object的映射:
| 參數(shù)類型 | 結(jié)果 |
|---|---|
| Undefined | 拋出TypeError |
| Null | 拋出TypeError |
| Number | 返回一個新的 Number 對象 |
| String | 返回一個新的 String 對象 |
| Boolean | 返回一個新的 Boolean 對象 |
| Symbol | 返回一個新的 Symbol 對象 |
| Object | 直接將Object返回 |
我們通常給Object.keys傳的參數(shù)都會是一個對象,但我們也可以來看看其它類型值的返回值會是怎樣的?
- Number
console.log(Object.keys(123)) // []
返回的是空數(shù)組,這是因為new Number(123)并沒有可提取的屬性

- String
console.log(Object.keys('123')) // [ '0', '1', '2' ]
字符串之所以返回的不是空數(shù)組,是因為new String('123')有可以提取的屬性

第二步:通過轉(zhuǎn)換后的對象獲得屬性列表properties。
(順序取決于這里)
對象屬性列表是通過 EnumerableOwnPropertyNames 獲取的,其中比較重要的是調(diào)用對象的內(nèi)部方法OwnPropertyKeys獲得對象的ownKeys(這些內(nèi)容可以在ECMAScript規(guī)范里面找到,就不展開介紹了,我們重點看排序)
The [[OwnPropertyKeys]] internal method of an ordinary object O takes no arguments. It performs the following steps when called:
Return ! OrdinaryOwnPropertyKeys(O).
通過上面的介紹,我們可以發(fā)現(xiàn)keys的排序取決于 OrdinaryOwnPropertyKeys(O)

翻譯過來就是:
- 創(chuàng)建一個空的列表用于存放 keys
- 將所有合法的數(shù)組索引按升序的順序存入
- 將所有字符串類型索引按屬性創(chuàng)建時間以升序的順序存入
- 將所有
Symbol類型索引按屬性創(chuàng)建時間以升序的順序存入 - 返回 keys
注意:屬性列表properties為List類型(List類型是ECMAScript規(guī)范類型)
第三步:將List類型的屬性列表properties轉(zhuǎn)換為Array得到最終的結(jié)果。
將List類型的屬性列表轉(zhuǎn)換成Array類型非常簡單:
- 先聲明一個變量
array,值是一個空數(shù)組 - 循環(huán)屬性列表,將每個元素添加到
array中 - 將
array返回
總結(jié)
Object.keys返回的對象屬性順序
- 將所有合法的數(shù)組索引按升序排序
- 將所有字符串類型索引按屬性創(chuàng)建時間以升序排序
- 將所有
Symbol類型索引按屬性創(chuàng)建時間以升序排序
合法數(shù)組索引指的是正整數(shù),負數(shù)或者浮點數(shù)一律當做字符串處理。嚴格來說對象屬性沒有數(shù)字類型的,無論是數(shù)字還是字符串,都會被當做字符串來處理。
看題
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))
經(jīng)過上面對Object.key特性的介紹,想必大家都不會再搞錯Object.keys的輸出順序了吧。
答案:
[ '1', '2', '-1', '1.1', 'c', 'b', 'a', 'd' ]
看到答案很多同學是不是有很多疑問?
如何理解對象屬性是正整數(shù)還是字符串?
首先我們上面說過合法數(shù)組索引指的是正整數(shù),負數(shù)或者浮點數(shù)一律當做字符串處理。嚴格來說對象屬性沒有數(shù)字類型的,無論是數(shù)字還是字符串,都會被當做字符串來處理。
所以上面只有1,'2',2是合法數(shù)組索引,但我們知道其實它們都會被轉(zhuǎn)成字符串,所以后面的2會將前面的'2'覆蓋,然后它們按升序排序。然后負數(shù)與浮點數(shù)一律當做字符串處理按屬性創(chuàng)建時間以升序排序。這樣就可以得到上面的答案了。
為什么沒有Symbol類型?
因為在 EnumerableOwnPropertyNames 的規(guī)范中規(guī)定了返回值只應(yīng)包含字符串屬性(上面說了數(shù)字其實也是字符串)。
我們也可以在MDN上查看關(guān)于 Object.getOwnPropertyNames() 的描述。
Object.getOwnPropertyNames() 方法返回一個由指定對象的所有自身屬性的屬性名(包括不可枚舉屬性但不包括 Symbol 值作為名稱的屬性)組成的數(shù)組。
所以 Symbol 屬性是不會被返回的,如果要返回 Symbol 屬性可以用 Object.getOwnPropertySymbols()。
以上就是Object.keys 詭異特性示例詳解的詳細內(nèi)容,更多關(guān)于Object.keys 詭異的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
20分鐘成功編寫bootstrap響應(yīng)式頁面 就這么簡單
這篇文章主要教大家如何在20分鐘內(nèi)成功編寫bootstrap響應(yīng)式頁面,其實很簡單,培養(yǎng)大家分分鐘開發(fā)出一個高大上的頁面能力,感興趣的小伙伴們可以參考一下2016-05-05
uni-app使用uni.navigateTo傳遞對象參數(shù)示例代碼
最近在做微信小程序用的是uniapp開發(fā)的,自己記錄一下,也和大家分享一下,這篇文章主要給大家介紹了關(guān)于uni-app使用uni.navigateTo傳遞對象參數(shù)的相關(guān)資料,需要的朋友可以參考下2023-11-11
js函數(shù)與php函數(shù)的區(qū)別實例淺析
這篇文章主要介紹了js函數(shù)與php函數(shù)的區(qū)別,以實例形式較為簡單的分析了js函數(shù)與php函數(shù)語法及應(yīng)用上的不同點,具有一定參考借鑒價值,需要的朋友可以參考下2015-01-01

