ES6+ Object.keys()
1. 前言
我們知道迭代對象可以使用 for...in
循環(huán)來做,但 for...in
循環(huán)會枚舉其原型鏈上的屬性,這使得我們在遍歷時需要判斷是不是原型鏈屬性。Object.keys()
可以接受一個對象返回一個可枚舉的數(shù)組,數(shù)組中的元素的排列順序和使用 for...in
循環(huán)遍歷返回的順序是一致的。
Object.keys()
在 ES5 中就有此方法,但是在設(shè)計上存在一定的缺陷,ES6 對其底層做了重大的更新。比如:在 ES5 中,如果此方法的參數(shù)不是對象(而是一個原始值),那么它會拋出 TypeError。在 ES2015 中,非對象的參數(shù)將被強(qiáng)制轉(zhuǎn)換為一個對象。
// ES5 代碼
Object.keys("imooc"); // TypeError: "imooc" is not an object
// ES6 代碼
Object.keys("imooc"); // ["0", "1", "2", "3", "4"]
現(xiàn)在的瀏覽器已經(jīng)基本都支持 ES6 的結(jié)果了,下面我們來系統(tǒng)性地認(rèn)識一下 Object.keys()
。
2. 方法詳情
2.1 基本語法
Object.keys()
方法會返回一個由一個給定對象的自身可枚舉屬性組成的數(shù)組,數(shù)組中屬性名的排列順序和正常循環(huán)遍歷該對象時返回的順序一致 。
語法使用:
Object.keys(obj)
參數(shù)解釋:
參數(shù) | 描述 |
---|---|
obj | 要返回其枚舉自身屬性的對象。 |
2.2 基本實(shí)例
用于對象時返回對象鍵值作為數(shù)組:
var obj = {
name: 'imooc',
type: 'ES6 Wiki'
}
console.log(Object.keys(obj));
// ["name", "type"]
用于數(shù)組類型:
var arr = ['a', 'b', 'c'];
console.log(Object.keys(arr));
// console: ['0', '1', '2']
也可以用于類數(shù)組中:
var obj = { 0: 'a', 1: 'b', 2: 'c' };
console.log(Object.keys(obj)); // ['0', '1', '2']
鍵值是數(shù)字和字符串混合時,會先進(jìn)行數(shù)值的排序,然后再按添加的順序排列字符串:
var obj = { name: 'imooc', 10: 'a', 3: 'b', age: 7 };
console.log(Object.keys(obj));
// ["3", "10", "name", "age"]
Object.keys()
不能獲取不可枚舉屬性:
// 創(chuàng)建一個obj對象帶有一個不可枚舉屬性
var obj = Object.create({}, {
getFoo: {
value: function () { return this.foo; }
}
});
obj.foo = 1;
console.log(Object.keys(obj)); // ['foo']
3. 自動排序問題
在說自動排序問題前,我們先來看下三個例子:
var obj1 = {99: '九十九', 5: '五', 7: '七'}
Object.keys(obj1) // ["5", "7", "99"]
var obj2 = {c: 'z', a: 'x', b: 'y'}
Object.keys(obj2) // ["c", "a", "b"]
var obj3 = { name: 'imooc', 10: 'a', 3: 'b', age: 7 };
Object.keys(obj3); // ["3", "10", "name", "age"]
上面的例子可以看出當(dāng)鍵值是數(shù)字時返回的值會自動排序,即使在混合情況下也會先進(jìn)行排序后把數(shù)字項(xiàng)放在數(shù)組中前面,而鍵值對是字符串時則不會被排序。那當(dāng) Object.keys()
被調(diào)用時內(nèi)部都發(fā)生了什么呢?
通過查閱 ECMA262 規(guī)范知道,Object.keys
在內(nèi)部會根據(jù)對象的屬性名 key
的類型進(jìn)行不同的排序邏輯。分三種情況:
- 如果屬性名的類型是
Number
,那么Object.keys
返回值是按照key
從小到大排序; - 如果屬性名的類型是
String
,那么Object.keys
返回值是按照屬性被創(chuàng)建的時間升序排序; - 如果屬性名的類型是
Symbol
,那么邏輯同String
相同。
那內(nèi)部到底發(fā)生了什么呢?
3.1 將參數(shù)轉(zhuǎn)換為對象
在 Object.keys()
調(diào)用時會根據(jù)傳入的參數(shù)進(jìn)行類型轉(zhuǎn)換,轉(zhuǎn)換為 Object 類型的值:
參數(shù)類型 | 結(jié)果 |
---|---|
Undefined | 拋出 TypeError |
Null | 拋出 TypeError |
Boolean | 返回一個新的 Boolean 對象 |
Number | 返回一個新的 Number 對象 |
String | 返回一個新的 String 對象 |
Symbol | 返回一個新的 Symbol 對象 |
Object | 直接將 Object 返回 |
實(shí)例:
// 參數(shù)是undefined或null
Object.keys(undefined) // Uncaught TypeError: Cannot convert undefined or null to object
// 參數(shù)是數(shù)值
Object.keys(123) // []
// 參數(shù)是字符串
Object.keys('imooc') // ["0", "1", "2", "3", "4"]
上面的代碼中,參數(shù)是數(shù)值時為什么會返回空數(shù)組呢?是因?yàn)閿?shù)值轉(zhuǎn)換為對象時沒有可提取的屬性,而字符串在 ES5 時會報錯,ES6 進(jìn)行了修復(fù),因?yàn)?String 對象有可提取的屬性??聪旅鎯蓮垐D:
3.2 獲取屬性列表
上面我們說到了 Object.keys()
會對參數(shù)做類型轉(zhuǎn)換,在獲取屬性的時候會調(diào)用內(nèi)部方法 EnumerableOwnProperties ( O, kind )
來計算對象上所有可枚舉的屬性 ownKeys
,這里的 ownKeys
類型時 list
類型,只用于內(nèi)部實(shí)現(xiàn)。
然后聲明變量用于存放遍歷對象后得到的屬性集合 properties
,properties
也是 List 類型,循環(huán)對象的 ownKeys
將每個元素添加到 properties
列表中。最后返回 properties
。
為什么會對數(shù)值進(jìn)行排序,是因?yàn)樵谡{(diào)用 EnumerableOwnProperties(O, kind)
方法執(zhí)行時,又會調(diào)用 OrdinaryOwnPropertyKeys(O)
,對于不同類型的屬性,會按不同的順序放入 properties
屬性列表中:
- 先處理類型為數(shù)值的屬性,從小到大放到屬性列表中;
- 再處理類型為字符串的屬性,按該屬性的創(chuàng)建順序,放到屬性列表中;
- 最后處理類型為
Symbol
的屬性,按創(chuàng)建順序,放到屬性列表中。
這樣就知道為什么會對數(shù)值進(jìn)行排序了,是 ECMA262 中 OrdinaryOwnPropertyKeys(o)
規(guī)定的。其原因是 OrdinaryOwnPropertyKeys(o)
內(nèi)部方法不只是給 Object.keys()
使用的,是通用的規(guī)則。
最后將 properties
列表轉(zhuǎn)化為數(shù)組就得到了 Object.keys()
的結(jié)果。
4. 小結(jié)
本節(jié)主要學(xué)習(xí)了 Object.keys()
方法用于獲取對象上可枚舉屬性,并返回屬性的數(shù)組,數(shù)組中的元素的排列順序和使用 for...in
循環(huán)遍歷返回的順序是一致的。這里需要注意的是,如果對象上的屬性是數(shù)值時,會被排序。