概述
在Javascript編程時尊惰,經(jīng)常需要遍歷對象的鍵碳却、值,ES5提供了for...in用來遍歷對象翅娶,然而其涉及對象屬性的“可枚舉屬性”文留、原型鏈屬性等,總會讓人多少摸不著頭腦竭沫。
本文將由Object對象本質(zhì)探尋各種遍歷對象的方法燥翅,并區(qū)分常用方法的特點。
本文所提的對象蜕提,特指Object的實例森书,不包含Set、Map谎势、Array等數(shù)據(jù)集對象凛膏。
剝開Object的“偽裝”
Javascript的對象,每一個屬性都有其“屬性描述符”它浅,主要有兩種形式:數(shù)據(jù)描述符和存取描述符译柏。
可以通過 Object.getOwnPropertyDescriptor
與 Object.getOwnPropertyDescriptors
兩個方法獲取對象的屬性描述符。
以下通過示例說明:
var obj = {
name: '10',
_age: 25,
get age(){
return this._age;
},
set age(age){
if(age<1){
throw new Error('Age must be more than 0');
}else{
this._age = age;
}
}
};
var des = Object.getOwnPropertyDescriptors(obj);
console.log(des);
/**
* des: {
* name: {
* configurable: true,
* enumerable: true,
* value: "10",
* writable: true,
* __proto__: Object
* },
* _age: {
* configurable: true,
* enumerable: true,
* value: 25,
* writable: true,
* __proto__: Object
* },
* age: {
* configurable: true,
* enumerable: true,
* get: f age(),
* set: f age(age),
* __proto__: Object
* },
* __proto__: Object
* }
*/
可以看到,
- name姐霍、_age擁有
'configurable'
鄙麦、'enumerable'
、'value'
镊折、'writable'
四個屬性描述符胯府,統(tǒng)稱數(shù)據(jù)描述符 - age擁有
'configurable'
、'enumerable'
恨胚、'get'
骂因、'set'
四個屬性描述符,統(tǒng)稱存取描述符
configurable | enumerable | value | writable | get | set | |
---|---|---|---|---|---|---|
數(shù)據(jù)描述符 | Yes | Yes | Yes | Yes | No | No |
存取描述符 | Yes | Yes | No | No | Yes | Yes |
對象的屬性描述符赃泡,可以通過Object.defineProperty
和Object.defineProperties
來修改(configurable
為true
的條件下)
詳細內(nèi)容可以參考:MDN手冊 Object.defineProperty
了解了這個之后寒波,與今天主題相關(guān)的乘盼,也就是 'enumerable'
這個屬性描述符啦,其值為 true
時俄烁,我們稱其為“可枚舉的”绸栅,屬性是否可枚舉影響了我們在使用原生方法遍歷對象時的結(jié)果,在本文后面页屠,將詳細說明粹胯。
掌握屬性描述符,無論是對自己以后的代碼編寫辰企,還是學習開源框架源碼风纠,都是十分基礎(chǔ)而且重要的,在此處僅作介紹牢贸,并著重關(guān)注與本文主題相關(guān)的屬性竹观。
常用遍歷方法
for..in..遍歷
遍歷自身及原型鏈上所有可枚舉的屬性
示例代碼:
var Person = function({name='none', age=18, height=170}={}){
this.name = name;
this.age = age;
this.height = height;
}
Person.prototype = {
type: 'Animal'
}
var qiu = new Person()
// 將height屬性設置為 不可枚舉
Object.defineProperty(qiu, 'height', {
enumerable: false
})
for(let n in qiu){
console.log(n);
}
// output: name age type
如以上代碼所示,使用for..in..遍歷十减,會將對象自身及其原型鏈上的所有可枚舉屬性全部遍歷出來栈幸。
而往往我們并不需要將原型鏈上的屬性也遍歷出來愤估,因此常常需要如下處理:
for(let n in qiu){
// 判斷是否實例自身擁有的屬性
if(qiu.hasOwnProperty(n)){
console.log(n)
}
}
因為for..in..在執(zhí)行的時候帮辟,還進行了原型鏈查找,當只需要遍歷對象自身的時候玩焰,性能上會收到一定影響由驹。
Object.keys遍歷
返回一個數(shù)組,包括對象自身的(不含繼承的)所有可枚舉屬性
示例代碼:
var Person = function({name='none', age=18, height=170}={}){
this.name = name;
this.age = age;
this.height = height;
}
Person.prototype = {
type: 'Animal'
}
var qiu = new Person()
// 將height屬性設置為 不可枚舉
Object.defineProperty(qiu, 'height', {
enumerable: false
})
var keys = Object.keys(qiu);
console.log(keys)
// output: ['name', 'age']
通過上述代碼,我們可以看到昔园,Object.keys僅遍歷對象本身蔓榄,并將所有可枚舉的屬性組合成一個數(shù)組返回。
在很多情況下默刚,其實我們需要的甥郑,也就是這樣一個功能。
例如以下荤西,將鍵值類型的查詢param轉(zhuǎn)換成url的query澜搅,不僅代碼量少、邏輯清晰邪锌,而且可以通過鏈式的寫法使得整體更加優(yōu)雅勉躺。
const searchObj = {
title: 'javascript',
author: 'Nicolas',
publishing: "O'RELLY",
language: 'cn'
}
let searchStr = Object.keys(searchObj)
.map(item => `${item}=${searchObj[item]}`)
.join('&');
let url = `localhost:8080/api/test?${searchStr}`
遍歷鍵值對的數(shù)據(jù)時,使用Object.keys真是不二之選觅丰。
Object.getOwnPropertyNames遍歷
返回一個數(shù)組饵溅,包含對象自身(不含繼承)的所有屬性名
示例代碼:
var Person = function({name='none', age=18, height=170}={}){
this.name = name;
this.age = age;
this.height = height;
}
Person.prototype = {
type: 'Animal'
}
var qiu = new Person()
// 將height屬性設置為 不可枚舉
Object.defineProperty(qiu, 'height', {
enumerable: false
})
var keys = Object.getOwnPropertyNames(qiu);
console.log(keys)
// output: ['name', 'age', 'height']
與Object.keys的區(qū)別在于Object.getOwnPropertyNames會把不可枚舉的屬性也返回。除此之外妇萄,與Object.keys的表現(xiàn)一致蜕企。
說好的for..of..咬荷,為什么無效
在ES6中新增了迭代器與for..of..的循環(huán)語法,在數(shù)組遍歷轻掩、Set萍丐、Map的遍歷上,十分方便放典。然而當我應用在對象(特指Object的實例 )上時(如下代碼)逝变,瀏覽器給我拋了一個異常:Uncaught TypeError: searchObj is not iterable
。
const searchObj = {
title: 'javascript',
author: 'Nicolas',
publishing: "O'RELLY",
language: 'cn'
}
for(let n of searchObj){
console.log(n)
}
// Uncaught TypeError: searchObj is not iterable
沒錯...這是一個錯誤的演示奋构,在ES6中壳影,對象默認下并不是可迭代對象,表現(xiàn)為其沒有[Symbol.iterator]屬性弥臼,可以通過以下代碼對比:
const searchObj = {
title: 'javascript',
author: 'Nicolas'
};
const bookList = ['javascript', 'java', 'c++'];
const nameSet = new Set(['Peter', 'Anna', 'Sue']);
console.log(searchObj[Symbol.iterator]); // undefined
console.log(bookList[Symbol.iterator]); // function values(){[native code]}
console.log(nameSet[Symbol.iterator]); // function values(){[native code]}
// 注宴咧,Set、Map径缅、Array的[Symbol.iterator]都是其原型對象上的方法掺栅,而非實例上的,這點需要注意
而for..of..循環(huán)纳猪,實際上是依次將迭代器(或任何可迭代的對象氧卧,如生成器函數(shù))的值賦予指定變量并進行循環(huán)的語法,當對象沒有默認迭代器的時候氏堤,當然不可以進行循環(huán)沙绝,而通過給對象增加一個默認迭代器,即[Symbol.iterator]屬性鼠锈,就可以實現(xiàn)闪檬,如下代碼:
Object.prototype[Symbol.iterator] = function *keys(){
for(let n of Object.keys(this)){ // 此處使用Object.keys獲取可枚舉的所有屬性
yield n
}
}
const searchObj = {
title: 'javascript',
author: 'Nicolas',
publishing: "O'RELLY",
language: 'cn',
};
for(let key of searchObj){
console.log(key)
}
// output: title author publishing language
以上代碼確實獲得了對象的所有鍵名,在生成器函數(shù)內(nèi)购笆,我們使用的是Object.keys獲得所有可枚舉的屬性值粗悯,然而這并不是所有人都期望的,也許小明期望不可枚舉的屬性值也被遍歷同欠,而小新可能連[Symbol.iterator]也希望遍歷出來样傍,于是,這里產(chǎn)生了一些分歧行您,如何遍歷有以下幾種因素:
總結(jié)起來铭乾,對象的property至少有三個方面的因素:
- 屬性是否可枚舉,即其 enumerable屬性描述符 的值娃循;
- 屬性的類型炕檩,是字符串類型、還是Symbol類型;
- 屬性所屬笛质,包含原型泉沾,還是僅僅包含實例本身;
鑒于各方意見不一妇押,并且現(xiàn)有的遍歷方式可以滿足跷究,于是標準組沒有將[Symbol.iterator]加入。
關(guān)于ES6迭代器敲霍、生成器的更多知識俊马,可以參考:ES6中的迭代器(Iterator)和生成器(Generator)