深入JS對象的遍歷

概述

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

本文所提的對象蜕提,特指Object的實例森书,不包含Set、Map谎势、Array等數(shù)據(jù)集對象凛膏。

剝開Object的“偽裝”

Javascript的對象,每一個屬性都有其“屬性描述符”它浅,主要有兩種形式:數(shù)據(jù)描述符存取描述符译柏。

可以通過 Object.getOwnPropertyDescriptorObject.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.definePropertyObject.defineProperties來修改(configurabletrue的條件下)
詳細內(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至少有三個方面的因素:

  1. 屬性是否可枚舉,即其 enumerable屬性描述符 的值娃循;
  2. 屬性的類型炕檩,是字符串類型、還是Symbol類型;
  3. 屬性所屬笛质,包含原型泉沾,還是僅僅包含實例本身;

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市肩杈,隨后出現(xiàn)的幾起案子柴我,更是在濱河造成了極大的恐慌,老刑警劉巖扩然,帶你破解...
    沈念sama閱讀 212,383評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件艘儒,死亡現(xiàn)場離奇詭異,居然都是意外死亡夫偶,警方通過查閱死者的電腦和手機界睁,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,522評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來兵拢,“玉大人翻斟,你說我怎么就攤上這事÷逊穑” “怎么了杨赤?”我有些...
    開封第一講書人閱讀 157,852評論 0 348
  • 文/不壞的土叔 我叫張陵敞斋,是天一觀的道長截汪。 經(jīng)常有香客問我,道長植捎,這世上最難降的妖魔是什么衙解? 我笑而不...
    開封第一講書人閱讀 56,621評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮焰枢,結(jié)果婚禮上蚓峦,老公的妹妹穿的比我還像新娘。我一直安慰自己济锄,他們只是感情好暑椰,可當我...
    茶點故事閱讀 65,741評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著荐绝,像睡著了一般一汽。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,929評論 1 290
  • 那天召夹,我揣著相機與錄音岩喷,去河邊找鬼。 笑死监憎,一個胖子當著我的面吹牛纱意,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播鲸阔,決...
    沈念sama閱讀 39,076評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼偷霉,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了褐筛?” 一聲冷哼從身側(cè)響起腾它,我...
    開封第一講書人閱讀 37,803評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎死讹,沒想到半個月后瞒滴,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,265評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡赞警,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,582評論 2 327
  • 正文 我和宋清朗相戀三年妓忍,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片愧旦。...
    茶點故事閱讀 38,716評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡世剖,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出笤虫,到底是詐尸還是另有隱情旁瘫,我是刑警寧澤,帶...
    沈念sama閱讀 34,395評論 4 333
  • 正文 年R本政府宣布琼蚯,位于F島的核電站酬凳,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏遭庶。R本人自食惡果不足惜宁仔,卻給世界環(huán)境...
    茶點故事閱讀 40,039評論 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望峦睡。 院中可真熱鬧翎苫,春花似錦、人聲如沸榨了。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,798評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽龙屉。三九已至呐粘,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背事哭。 一陣腳步聲響...
    開封第一講書人閱讀 32,027評論 1 266
  • 我被黑心中介騙來泰國打工漫雷, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人鳍咱。 一個月前我還...
    沈念sama閱讀 46,488評論 2 361
  • 正文 我出身青樓降盹,卻偏偏與公主長得像,于是被迫代替她去往敵國和親谤辜。 傳聞我的和親對象是個殘疾皇子蓄坏,可洞房花燭夜當晚...
    茶點故事閱讀 43,612評論 2 350

推薦閱讀更多精彩內(nèi)容

  • 1.屬性的簡潔表示法 允許直接寫入變量和函數(shù) 上面代碼表明,ES6 允許在對象之中丑念,直接寫變量涡戳。這時,屬性名為變量...
    雨飛飛雨閱讀 1,130評論 0 3
  • 官方中文版原文鏈接 感謝社區(qū)中各位的大力支持脯倚,譯者再次奉上一點點福利:阿里云產(chǎn)品券渔彰,享受所有官網(wǎng)優(yōu)惠,并抽取幸運大...
    HetfieldJoe閱讀 2,587評論 9 22
  • 屬性的簡潔表示法 ES6允許直接寫入變量和函數(shù)推正,作為對象的屬性和方法恍涂。這樣的書寫更加簡潔。 上面代碼表明植榕,ES6允...
    呼呼哥閱讀 2,911評論 0 2
  • 別因為某一段愛情的消逝尊残,就不再相信愛情炒瘸。所謂真愛,是一條流動的長河寝衫, 而我們顷扩,是跨河而過的人。 白百何出軌的消...
    梁慢慢小姐閱讀 406評論 0 0
  • 凱風西吹自楚來竞端,銘諸肺腑當年誼屎即。 厚情連綿十一年,霖瀝共憫故鄉(xiāng)情事富。 青山依舊顏未改,霞云孤鶩共秋水乘陪。 曉月清風半里...
    朱大餅閱讀 295評論 0 1