深入JS對(duì)象的遍歷

概述

在Javascript編程時(shí),經(jīng)常需要遍歷對(duì)象的鍵、值,ES5提供了for...in用來遍歷對(duì)象,然而其涉及對(duì)象屬性的“可枚舉屬性”、原型鏈屬性等,總會(huì)讓人多少摸不著頭腦。
本文將由Object對(duì)象本質(zhì)探尋各種遍歷對(duì)象的方法,并區(qū)分常用方法的特點(diǎn)。

本文所提的對(duì)象,特指Object的實(shí)例,不包含Set、Map、Array等數(shù)據(jù)集對(duì)象。

剝開Object的“偽裝”

Javascript的對(duì)象,每一個(gè)屬性都有其“屬性描述符”,主要有兩種形式:數(shù)據(jù)描述符存取描述符

可以通過 Object.getOwnPropertyDescriptorObject.getOwnPropertyDescriptors兩個(gè)方法獲取對(duì)象的屬性描述符。
以下通過示例說明:

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'四個(gè)屬性描述符,統(tǒng)稱數(shù)據(jù)描述符
  • age擁有'configurable'、'enumerable'、'get'、'set'四個(gè)屬性描述符,統(tǒng)稱存取描述符
configurable enumerable value writable get set
數(shù)據(jù)描述符 Yes Yes Yes Yes No No
存取描述符 Yes Yes No No Yes Yes

對(duì)象的屬性描述符,可以通過Object.definePropertyObject.defineProperties來修改(configurabletrue的條件下)
詳細(xì)內(nèi)容可以參考:MDN手冊(cè) Object.defineProperty

了解了這個(gè)之后,與今天主題相關(guān)的,也就是 'enumerable'這個(gè)屬性描述符啦,其值為 true 時(shí),我們稱其為“可枚舉的”,屬性是否可枚舉影響了我們?cè)谑褂迷椒ū闅v對(duì)象時(shí)的結(jié)果,在本文后面,將詳細(xì)說明。

掌握屬性描述符,無論是對(duì)自己以后的代碼編寫,還是學(xué)習(xí)開源框架源碼,都是十分基礎(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屬性設(shè)置為 不可枚舉
Object.defineProperty(qiu, 'height', {
  enumerable: false
})

for(let n in qiu){
  console.log(n);
}

// output: name age type

如以上代碼所示,使用for..in..遍歷,會(huì)將對(duì)象自身及其原型鏈上的所有可枚舉屬性全部遍歷出來。
而往往我們并不需要將原型鏈上的屬性也遍歷出來,因此常常需要如下處理:

for(let n in qiu){
  // 判斷是否實(shí)例自身擁有的屬性
  if(qiu.hasOwnProperty(n)){
    console.log(n)
  }
}

因?yàn)閒or..in..在執(zhí)行的時(shí)候,還進(jìn)行了原型鏈查找,當(dāng)只需要遍歷對(duì)象自身的時(shí)候,性能上會(huì)收到一定影響。

Object.keys遍歷

返回一個(gè)數(shù)組,包括對(duì)象自身的(不含繼承的)所有可枚舉屬性

示例代碼:

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屬性設(shè)置為 不可枚舉
Object.defineProperty(qiu, 'height', {
  enumerable: false
})

var keys = Object.keys(qiu);
console.log(keys)
// output: ['name', 'age']

通過上述代碼,我們可以看到,Object.keys僅遍歷對(duì)象本身,并將所有可枚舉的屬性組合成一個(gè)數(shù)組返回。
在很多情況下,其實(shí)我們需要的,也就是這樣一個(gè)功能。
例如以下,將鍵值類型的查詢param轉(zhuǎn)換成url的query,不僅代碼量少、邏輯清晰,而且可以通過鏈?zhǔn)降膶懛ㄊ沟谜w更加優(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}`

遍歷鍵值對(duì)的數(shù)據(jù)時(shí),使用Object.keys真是不二之選。

Object.getOwnPropertyNames遍歷

返回一個(gè)數(shù)組,包含對(duì)象自身(不含繼承)的所有屬性名

示例代碼:

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屬性設(shè)置為 不可枚舉
Object.defineProperty(qiu, 'height', {
  enumerable: false
})

var keys = Object.getOwnPropertyNames(qiu);
console.log(keys)
// output: ['name', 'age', 'height']

與Object.keys的區(qū)別在于Object.getOwnPropertyNames會(huì)把不可枚舉的屬性也返回。除此之外,與Object.keys的表現(xiàn)一致。

說好的for..of..,為什么無效

在ES6中新增了迭代器與for..of..的循環(huán)語法,在數(shù)組遍歷、Set、Map的遍歷上,十分方便。然而當(dāng)我應(yīng)用在對(duì)象(特指Object的實(shí)例 )上時(shí)(如下代碼),瀏覽器給我拋了一個(gè)異常: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

沒錯(cuò)...這是一個(gè)錯(cuò)誤的演示,在ES6中,對(duì)象默認(rèn)下并不是可迭代對(duì)象,表現(xiàn)為其沒有[Symbol.iterator]屬性,可以通過以下代碼對(duì)比:

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]都是其原型對(duì)象上的方法,而非實(shí)例上的,這點(diǎn)需要注意

而for..of..循環(huán),實(shí)際上是依次將迭代器(或任何可迭代的對(duì)象,如生成器函數(shù))的值賦予指定變量并進(jìn)行循環(huán)的語法,當(dāng)對(duì)象沒有默認(rèn)迭代器的時(shí)候,當(dāng)然不可以進(jìn)行循環(huán),而通過給對(duì)象增加一個(gè)默認(rèn)迭代器,即[Symbol.iterator]屬性,就可以實(shí)現(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í)獲得了對(duì)象的所有鍵名,在生成器函數(shù)內(nèi),我們使用的是Object.keys獲得所有可枚舉的屬性值,然而這并不是所有人都期望的,也許小明期望不可枚舉的屬性值也被遍歷,而小新可能連[Symbol.iterator]也希望遍歷出來,于是,這里產(chǎn)生了一些分歧,如何遍歷有以下幾種因素:
總結(jié)起來,對(duì)象的property至少有三個(gè)方面的因素:

  1. 屬性是否可枚舉,即其 enumerable屬性描述符 的值;
  2. 屬性的類型,是字符串類型、還是Symbol類型;
  3. 屬性所屬,包含原型,還是僅僅包含實(shí)例本身;

鑒于各方意見不一,并且現(xiàn)有的遍歷方式可以滿足,于是標(biāo)準(zhǔn)組沒有將[Symbol.iterator]加入。
關(guān)于ES6迭代器、生成器的更多知識(shí),可以參考:ES6中的迭代器(Iterator)和生成器(Generator)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 1.屬性的簡潔表示法 允許直接寫入變量和函數(shù) 上面代碼表明,ES6 允許在對(duì)象之中,直接寫變量。這時(shí),屬性名為變量...
    雨飛飛雨閱讀 1,254評(píng)論 0 3
  • 官方中文版原文鏈接 感謝社區(qū)中各位的大力支持,譯者再次奉上一點(diǎn)點(diǎn)福利:阿里云產(chǎn)品券,享受所有官網(wǎng)優(yōu)惠,并抽取幸運(yùn)大...
    HetfieldJoe閱讀 2,675評(píng)論 9 22
  • 屬性的簡潔表示法 ES6允許直接寫入變量和函數(shù),作為對(duì)象的屬性和方法。這樣的書寫更加簡潔。 上面代碼表明,ES6允...
    呼呼哥閱讀 3,008評(píng)論 0 2
  • 別因?yàn)槟骋欢螑矍榈南牛筒辉傧嘈艕矍?。所謂真愛,是一條流動(dòng)的長河, 而我們,是跨河而過的人。 白百何出軌的消...
    梁慢慢小姐閱讀 547評(píng)論 0 0
  • 凱風(fēng)西吹自楚來,銘諸肺腑當(dāng)年誼。 厚情連綿十一年,霖瀝共憫故鄉(xiāng)情。 青山依舊顏未改,霞云孤鶩共秋水。 曉月清風(fēng)半里...
    朱大餅閱讀 326評(píng)論 0 1

友情鏈接更多精彩內(nèi)容