遍歷得到數(shù)組 Or Iterator 遍歷器?
一、背景
故事的開頭是這樣的...
在遍歷數(shù)組與對象屬性時,對使用 obj.keys()、obj.values()和 obj.entries() 還是 Object.keys(obj)、Object.values(obj)、Object.entries(obj)方法產(chǎn)生了一些困惑。話不多說,先放問題:
需求:想要遍歷一個對象,并獲取遍歷對象的屬性值 實(shí)現(xiàn):Object.keys()、Object.values() 和 Object.entries() 方法 問題:一不小心同數(shù)組的 entries(),keys()和 values() 方法混淆了~QAQ
二、keys()、values()、entries()遍歷方法
熟悉 ES 語法數(shù)據(jù)結(jié)構(gòu)的朋友一定很清楚,原生對象數(shù)據(jù)結(jié)構(gòu)并不支持 obj.keys()、obj.values()和 obj.entries() 方法,數(shù)組與 map、set 等數(shù)據(jù)結(jié)構(gòu)才支持。但仍可以通過 Object.keys(obj)、Object.values(obj)、Object.entries(obj)獲取原生對象中可遍歷的屬性組成數(shù)組類型數(shù)據(jù)結(jié)構(gòu)。
也就是說,keys()、values()和 entries() 方法有兩種:
ES5-ES2017 相繼引入 Object.keys 、Object.values 和 Object.entries 方法,返回一個數(shù)組,成員是參數(shù)對象自身的(不含繼承的)所有可遍歷(enumerable)屬性的鍵名/鍵值/鍵值對,可以用 for...of 循環(huán)進(jìn)行遍歷;
ES6 提供 entries(),keys() 和 values() -- 可用于遍歷數(shù)組/Map/Set 等類數(shù)組數(shù)據(jù)結(jié)構(gòu)實(shí)例,返回一個(Iterator)遍歷器對象,可以用 for...of 循環(huán)進(jìn)行遍歷。
注意這里又有兩點(diǎn)區(qū)別:
兩者調(diào)用語法不同,顯而易見;
前者返回的是一個可迭代的對象,而后者返回的是一個真正的數(shù)組。
有沒有被繞暈?那我們先來看第一個問題吧 -- 調(diào)用語法的不同
Q1: Object.keys 、Object.values 和 Object.entries 方法
為了區(qū)分這兩種調(diào)用語法,我們必須得來回顧下原型鏈的相關(guān)知識。
因?yàn)檫@里的 entries(),keys()和 values() 方法正是是調(diào)用原型對象構(gòu)造函數(shù)上的方法。如下圖可以看到,對于一個普通對象,這三個方法在 Object 對象的[[prototype]]下的 constructor 中:
而對于一個數(shù)組結(jié)構(gòu)來說,這三個方法可以在數(shù)組原型鏈中和原型鏈上層對象原型的 constructor 中同時找到:
即 Object.keys(arr)調(diào)用的是數(shù)組原型鏈頂層原型對象 constructor 的方法,而數(shù)組本身也支持的 arr.keys()方法,則是調(diào)用數(shù)組原型鏈上的方法。
即對象只支持前種調(diào)用方式,而數(shù)組同時支持這兩種調(diào)用:
同時我們知道在 JavaScript 中,對象是所有復(fù)雜結(jié)構(gòu)的基礎(chǔ)。也正對應(yīng)了其他復(fù)雜結(jié)構(gòu)原型鏈的頂端是對象原型結(jié)構(gòu)?,F(xiàn)在應(yīng)該能夠知道為何普通對象不支持 obj.keys()、obj.values()和 obj.entries() 方法了,但到這里就不得不提出另一個疑問了:
Q2: 如何讓一個對象支持 obj.keys()、obj.values()和 obj.entries() 方法呢?
理論上,我們是可以為一個對象構(gòu)造任意方法,那么如何實(shí)現(xiàn)和數(shù)組一樣的遍歷方法呢?本質(zhì)上這個方法是能夠生成一個遍歷器。
- let objE = {
 - data: [ 'hello', 'world' ],
 - keys: function() {
 - const self = this;
 - return {
 - [Symbol.iterator]() {
 - let index = 0;
 - return {
 - next() {
 - if (index < self.data.length) {
 - return {
 - value: self.data[index++],
 - done: false
 - };
 - }
 - return { value: undefined, done: true };
 - }
 - };
 - }
 - }
 - }
 - };
 
上述,我們自己創(chuàng)建了一個 data 對象,并實(shí)現(xiàn)了它自己的 data.values() 方法。同時,我們依然可以對它調(diào)用 Object.values(data) 方法。
從上面的方法不難看出,我們在對象中通過添加 Symbol.iterator 手動構(gòu)造了一個輸出遍歷器函數(shù),關(guān)于遍歷器的討論我們在下一節(jié)討論,現(xiàn)在先來討論調(diào)用返回結(jié)果的區(qū)別。
Q3: 兩種調(diào)用方法返回結(jié)果:遍歷器與數(shù)組
1)第一種調(diào)用方法,根據(jù)定義可知:返回一個數(shù)組,數(shù)組成員是參數(shù)對象自身的(不含繼承的)所有可遍歷(enumerable)屬性的鍵名/鍵值/鍵值對。
敲重點(diǎn)!!!這三個方法只返回對象自身的可遍歷屬性,即屬性描述對象的 enumerable 為 true。
我們可以通過 for ... in 循環(huán)來實(shí)現(xiàn)相同的遍歷效果。
2)而第二種方法,返回一個遍歷器:顧名思義,遍歷器也可以滿足循環(huán)遍歷的需求。
本質(zhì)上,遍歷器的定義是一種接口,為各種不同的數(shù)據(jù)結(jié)構(gòu)提供統(tǒng)一的訪問機(jī)制。接下來就來了解下適用于不同數(shù)據(jù)結(jié)構(gòu)的遍歷器。
三、Iterator 遍歷器
首先我們知道,目前主要有四種表示“集合”的數(shù)據(jù)結(jié)構(gòu):數(shù)組(Array)、對象(Object)、Map 和 Set,這里表示"集合"的對象例如 NodeList 集合類數(shù)組對象,而遍歷器可以使我們遍歷訪問這些集合。
實(shí)際上,原生具備 Iterator 接口的數(shù)據(jù)結(jié)構(gòu)包括 Array、Map、Set、String、TypedArray、函數(shù)的 arguments 對象和 NodeList 對象。
具體遍歷器的概念可參考阮一峰老師 ES6 入門 Iterator 一章,已經(jīng)十分詳細(xì)清楚:
因此,Iterator 遍歷器本質(zhì)上為所有數(shù)據(jù)結(jié)構(gòu),提供了一種統(tǒng)一的訪問機(jī)制,即 for...of 循環(huán)。
關(guān)于遍歷,我們前面已經(jīng)講到了遍歷對象屬性,這里再提一嘴:
1. 遍歷類數(shù)組對象/Array/Map/Set 等數(shù)組數(shù)據(jù)結(jié)構(gòu)實(shí)例
當(dāng)使用 for...of 循環(huán)遍歷某種數(shù)據(jù)結(jié)構(gòu)時,該循環(huán)會自動去尋找 Iterator 接口。一種數(shù)據(jù)結(jié)構(gòu)只要部署了 Iterator 接口,我們就稱這種數(shù)據(jù)結(jié)構(gòu)是“可遍歷的”(iterable)。ES6 規(guī)定,默認(rèn)的 Iterator 接口部署在數(shù)據(jù)結(jié)構(gòu)的 Symbol.iterator 屬性,換句話說,一個數(shù)據(jù)結(jié)構(gòu)只要具有 Symbol.iterator 屬性,就可以認(rèn)為是“可迭代/遍歷的”(iterable)。
2. 獲取對象可遍歷屬性
Object.keys 、Object.values 和 Object.entries 方法只返回對象自身的可遍歷屬性,通過屬性描述對象的 enumerable 標(biāo)識改對象屬性是否可以遍歷。同時因?yàn)槠胀▽ο?not iterable,即普通對象不具有 Symbol.iterator 屬性,所以無法通過 for...of 循環(huán)直接遍歷,否則會報(bào)錯 Uncaught TypeError: obj is not iterable。
可見,數(shù)組及類數(shù)組的遍歷(迭代)與普通對象中的提到的遍歷是不同的,這分別取決于各自的 iterable 和 enumerable 屬性。
3. for ... of
ES6 中引入 for...of 循環(huán),很多時候用以替代 for...in 和 forEach() ,并支持新的迭代協(xié)議。for...of 語句在可迭代對象上創(chuàng)建一個迭代循環(huán),調(diào)用自定義迭代鉤子,并為每個不同屬性的值執(zhí)行語句。
那么終極問題:如何實(shí)現(xiàn) Symbol.iterator 方法,使普通對象可被 for of 迭代?其實(shí)在 Q2 部分已經(jīng)實(shí)現(xiàn)了。
嘗試給普通對象實(shí)現(xiàn)一個 Symbol.iterator 接口:
- // 普通對象
 - const obj = {
 - foo: 'value1',
 - bar: 'value2',
 - [Symbol.iterator]() {
 - // 這里 Object.keys 不會獲取到 Symbol.iterator 屬性
 - const keys = Object.keys(obj); // 得到一個數(shù)組
 - let index = 0;
 - return {
 - next: () => {
 - if (index < keys.length) {
 - // 迭代結(jié)果 未結(jié)束
 - return {
 - value: this[keys[index++]],
 - done: false
 - };
 - } else {
 - // 迭代結(jié)果 結(jié)束
 - return { value: undefined, done: true };
 - }
 - }
 - };
 - }
 - }
 - for (const value of obj) {
 - console.log(value); // value1 value2
 - };
 
for...of 循環(huán)內(nèi)部調(diào)用的是數(shù)據(jù)結(jié)構(gòu)的 Symbol.iterator 方法,for...of 循環(huán)可以使用的范圍包括數(shù)組、Set 和 Map 結(jié)構(gòu)、某些類似數(shù)組的對象(比如 arguments 對象、DOM NodeList 對象)、后文的 Generator 對象,以及字符串。
for...of 循環(huán)作為 ES6 新引入的一種循環(huán),具有以下明顯優(yōu)勢(按需使用):
有著同 for...in 一樣的簡潔語法,但是沒有 for...in 那些缺點(diǎn)(無序,不適用于遍歷數(shù)組)。
不同于 forEach 方法,它可以與 break、continue 和 return 配合使用。
提供了遍歷所有數(shù)據(jù)結(jié)構(gòu)的統(tǒng)一操作接口。
以上是我從 keys()、values()、entries() 遍歷方法出發(fā)對遍歷器產(chǎn)生的幾點(diǎn)思考,如有不足之處,歡迎指正~~~




















 
 
 





 
 
 
 