Iterator和for...of循環(huán)

Iterator(遍歷器)

概念

表示"集合"的數(shù)據(jù)結(jié)構(gòu),主要是原有的ArrayObject,ES6增加的MapSet.需要一個(gè)統(tǒng)一的接口機(jī)制來(lái)處理所有不同的數(shù)據(jù)結(jié)構(gòu)
任何數(shù)據(jù)結(jié)構(gòu)只要部署Iterator接口,就可以完成遍歷操作(即依次處理該數(shù)據(jù)結(jié)構(gòu)的所有成員)
作用主要有三點(diǎn):

  1. 為各種數(shù)據(jù)結(jié)構(gòu)提供一個(gè)統(tǒng)一的,簡(jiǎn)便的訪問(wèn)接口
  2. 使得數(shù)據(jù)結(jié)構(gòu)的成員能夠按照某種次序排列
  3. 方便使用for...of使用
    遍歷原理主要是創(chuàng)建一個(gè)指針對(duì)象,指向當(dāng)前數(shù)據(jù)結(jié)構(gòu)的起始位置,不斷調(diào)用指針對(duì)象的next方法,每次調(diào)用next方法,都會(huì)返回?cái)?shù)據(jù)結(jié)構(gòu)的當(dāng)前成員的信息.具體來(lái)說(shuō)就是返回一個(gè)包含valuedone兩個(gè)屬性的對(duì)象,其中,value屬性是當(dāng)前成員的值,done屬性是一個(gè)布爾值,表示遍歷是否結(jié)束

默認(rèn)Iterator接口

一個(gè)數(shù)據(jù)結(jié)構(gòu)只要部署了Iterator接口,我們就稱這種數(shù)據(jù)結(jié)構(gòu)是"可遍歷的"
ES6規(guī)定,默認(rèn)的Iterator接口部署在數(shù)據(jù)結(jié)構(gòu)的Symbol.iterator屬性,Symbol.iterator熟悉本身就是一個(gè)函數(shù),就是當(dāng)前數(shù)據(jù)結(jié)構(gòu)默認(rèn)的遍歷器生成函數(shù).執(zhí)行這個(gè)函數(shù),就會(huì)返回一個(gè)遍歷器.至于屬性名Symbol.iterator,它是一個(gè)表達(dá)式,返回Symbol對(duì)象的iterator屬性,這是一個(gè)預(yù)定義好的,類型為Symbol的特殊值,所以要放在方括號(hào)內(nèi)

const obj = {
  [Symbol.iterator] : function () {
    return {
      next: function () {
        return {
          value: 1,
          done: true
        };
      }
    };
  }
};

ES6的有些數(shù)據(jù)結(jié)構(gòu)原生具備Iterator接口,即不用任何處理,就可以被for...of循環(huán)遍歷
原生具備Iterator接口的數(shù)據(jù)結(jié)構(gòu)如下:

  • Array
  • Map
  • Set
  • String
  • TypedArray
  • 函數(shù)的arguments對(duì)象
  • NodeList對(duì)象

下面的例子是數(shù)組的Symbol.iterator屬性

let arr = ['a', 'b', 'c'];
let iter = arr[Symbol.iterator]();

iter.next() // { value: 'a', done: false }
iter.next() // { value: 'b', done: false }
iter.next() // { value: 'c', done: false }
iter.next() // { value: undefined, done: true }

除了原生部署Iterator接口的數(shù)據(jù)結(jié)構(gòu)之外,其它數(shù)據(jù)結(jié)構(gòu)(主要是對(duì)象)的Iterator接口,都需要在Symbol.iterator屬性上面部署
對(duì)象之所以沒有默認(rèn)部署,是因?yàn)閷?duì)象的哪個(gè)屬性先遍歷,哪個(gè)屬性后遍歷是不確定的.本質(zhì)上,遍歷器是一種線性處理,對(duì)于任何非線性的數(shù)據(jù)接口,都必須部署遍歷器接口,就等于部署一種線性轉(zhuǎn)換.(對(duì)象部署遍歷器接口不是很必要,因?yàn)榇藭r(shí)對(duì)象實(shí)際上被當(dāng)做Map結(jié)構(gòu)使用)

class RangeIterator {
  constructor(start, stop) {
    this.value = start;
    this.stop = stop;
  }

  [Symbol.iterator]() { return this; }

  next() {
    var value = this.value;
    if (value < this.stop) {
      this.value++;
      return {done: false, value: value};
    }
    return {done: true, value: undefined};
  }
}

function range(start, stop) {
  return new RangeIterator(start, stop);
}

for (var value of range(0, 3)) {
  console.log(value); // 0, 1, 2
}

下面是為一個(gè)對(duì)象增加Iterator接口的例子

let obj = {
  data: [ 'hello', 'world' ],
  [Symbol.iterator]() {
    const self = this;
    let index = 0;
    return {
      next() {
        if (index < self.data.length) {
          return {
            value: self.data[index++],
            done: false
          };
        } else {
          return { value: undefined, done: true };
        }
      }
    };
  }
};

對(duì)于類似數(shù)組的對(duì)象(存在數(shù)值鍵名和length屬性),部署Iterator接口,有一個(gè)簡(jiǎn)便方法,就是Symbol.iterator方法直接引用數(shù)組的Iterator接口

NodeList.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];
// 或者
NodeList.prototype[Symbol.iterator] = [][Symbol.iterator];

[...document.querySelectorAll('div')] // 可以執(zhí)行了

注意,普通對(duì)象部署數(shù)組的Symbol.iterator方法,并無(wú)效果
如果Symbol.iterator方法對(duì)應(yīng)的不是遍歷器生成函數(shù),解釋引擎將會(huì)報(bào)錯(cuò)

調(diào)用Iterator接口的場(chǎng)合

有一些場(chǎng)合默認(rèn)調(diào)用Iterator接口

  1. 解構(gòu)賦值
    對(duì)數(shù)組和Set解構(gòu)進(jìn)行解構(gòu)賦值時(shí)
  2. 拓展運(yùn)算符
    只要某個(gè)數(shù)據(jù)結(jié)構(gòu)部署了Iterator接口,就可以對(duì)它使用拓展運(yùn)算符,將其轉(zhuǎn)為數(shù)組
  3. yield*
  4. 其它場(chǎng)合
    又要數(shù)組的遍歷會(huì)調(diào)用遍歷器接口,所以任何接受數(shù)組作為參數(shù)的場(chǎng)合,其實(shí)都調(diào)用了遍歷器接口
    • for...of
    • Array.from
    • Map,Set,WeakMap,WeakSet
    • Promise.all
    • Promise.race

Iterator接口與Generator函數(shù)

Symbol.iterator方法的最簡(jiǎn)單實(shí)現(xiàn),還是使用Generator函數(shù)

let myIterable = {
  [Symbol.iterator]: function* () {
    yield 1;
    yield 2;
    yield 3;
  }
}
[...myIterable] // [1, 2, 3]

// 或者采用下面的簡(jiǎn)潔寫法

let obj = {
  * [Symbol.iterator]() {
    yield 'hello';
    yield 'world';
  }
};

for (let x of obj) {
  console.log(x);
}
// "hello"
// "world"

遍歷器對(duì)象的return()和throw()

return方法和throw方法是否部署是可選的
return方法的使用場(chǎng)合是,如果for...of循環(huán)提前退出(通常是因?yàn)槌鲥e(cuò),或者有break語(yǔ)句或continue語(yǔ)句),就會(huì)調(diào)用return方法.如果一個(gè)對(duì)象在完成遍歷前,需要清理或釋放資源,就可以部署return方法

function readLinesSync(file) {
  return {
    [Symbol.iterator]() {
      return {
        next() {
          return { done: false };
        },
        return() {
          file.close();
          return { done: true };
        }
      };
    },
  };
}

注意:return方法必須返回一個(gè)對(duì)象
throw方法主要配合Generator函數(shù)使用,一般的遍歷器對(duì)象用不到這個(gè)方法

for...of循環(huán)

概念

引入for...of循環(huán)作為遍歷所有數(shù)據(jù)結(jié)構(gòu)的統(tǒng)一方法
JavaScript原有的for...in循環(huán),只能獲得對(duì)象的鍵名,不能直接獲取鍵值.ES6提供for...of循環(huán),允許遍歷獲得鍵值
for...of循環(huán)調(diào)用遍歷接口,數(shù)組的遍歷器接口只返回具有數(shù)字索引的屬性.這一點(diǎn)跟for...in循環(huán)不一樣

Set和Map結(jié)構(gòu)

遍歷Set結(jié)構(gòu)和Map結(jié)構(gòu),注意的地方有兩點(diǎn):首先,遍歷的順序是按照各個(gè)成員被添加進(jìn)數(shù)據(jù)結(jié)構(gòu)的順序.其次,Set結(jié)構(gòu)遍歷時(shí),返回的是一個(gè)值,而Map結(jié)構(gòu)遍歷時(shí),返回的是一個(gè)數(shù)組

計(jì)算生成的數(shù)據(jù)結(jié)構(gòu)

ES6的數(shù)組,Set,Map都部署了以下三個(gè)方法,調(diào)用后都返回遍歷器對(duì)象

  • entries():遍歷器對(duì)象用來(lái)遍歷[鍵名,鍵值]組成的數(shù)組.對(duì)于數(shù)組,鍵名就是索引值;對(duì)于Set,鍵名與鍵值相同;Map結(jié)構(gòu)的Iterator接口,默認(rèn)就是調(diào)用該方法.
  • keys()
  • values()

類似數(shù)組的對(duì)象

對(duì)于字符串來(lái)說(shuō),for...of有一個(gè)特點(diǎn),就是會(huì)正確識(shí)別32位UTF-16字符
可以使用Array.from方法將類似數(shù)組的對(duì)象轉(zhuǎn)為數(shù)組

let arrayLike = { length: 2, 0: 'a', 1: 'b' };

// 報(bào)錯(cuò)
for (let x of arrayLike) {
  console.log(x);
}

// 正確
for (let x of Array.from(arrayLike)) {
  console.log(x);
}

對(duì)象

對(duì)于普通的對(duì)象,for...of結(jié)構(gòu)不能直接使用,必須部署了Iterator接口后才能使用
一種解決方法是,使用Object.keys方法將對(duì)象的鍵名生成一個(gè)數(shù)組

for (var key of Object.keys(someObject)) {
  console.log(key + ': ' + someObject[key]);
}

另一個(gè)方法是使用Generator函數(shù)將對(duì)象重新包裝一下

function* entries(obj) {
  for (let key of Object.keys(obj)) {
    yield [key, obj[key]];
  }
}

for (let [key, value] of entries(obj)) {
  console.log(key, '->', value);
}
// a -> 1
// b -> 2
// c -> 3

與其他遍歷語(yǔ)法的比較

forEach無(wú)法中途跳出循環(huán)
for...in有幾個(gè)缺點(diǎn):
1.以字符串作為鍵名"0","1","2"等
2.不僅遍歷數(shù)組鍵名,還會(huì)遍歷手動(dòng)添加的其他鍵,甚至包括原型鏈上的鍵
3.某些情況下,會(huì)以任意順序遍歷鍵名
for...of有一些顯著的優(yōu)點(diǎn):
1.沒有for...in那些缺點(diǎn)
2.不同于forEach方法,它可以與break,continue,return配合使用
3.提供了遍歷所有數(shù)據(jù)結(jié)構(gòu)的統(tǒng)一操作接口

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子徘键,更是在濱河造成了極大的恐慌斧吐,老刑警劉巖恳不,帶你破解...
    沈念sama閱讀 217,185評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件交惯,死亡現(xiàn)場(chǎng)離奇詭異浅妆,居然都是意外死亡墅诡,警方通過(guò)查閱死者的電腦和手機(jī)壳嚎,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)末早,“玉大人烟馅,你說(shuō)我怎么就攤上這事∪涣祝” “怎么了郑趁?”我有些...
    開封第一講書人閱讀 163,524評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)姿搜。 經(jīng)常有香客問(wèn)我寡润,道長(zhǎng)捆憎,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,339評(píng)論 1 293
  • 正文 為了忘掉前任梭纹,我火速辦了婚禮躲惰,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘栗柒。我一直安慰自己礁扮,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,387評(píng)論 6 391
  • 文/花漫 我一把揭開白布瞬沦。 她就那樣靜靜地躺著,像睡著了一般雇锡。 火紅的嫁衣襯著肌膚如雪逛钻。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,287評(píng)論 1 301
  • 那天锰提,我揣著相機(jī)與錄音曙痘,去河邊找鬼。 笑死立肘,一個(gè)胖子當(dāng)著我的面吹牛边坤,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播谅年,決...
    沈念sama閱讀 40,130評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼茧痒,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了融蹂?” 一聲冷哼從身側(cè)響起旺订,我...
    開封第一講書人閱讀 38,985評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎超燃,沒想到半個(gè)月后区拳,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,420評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡意乓,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,617評(píng)論 3 334
  • 正文 我和宋清朗相戀三年樱调,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片届良。...
    茶點(diǎn)故事閱讀 39,779評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡笆凌,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出伙窃,到底是詐尸還是另有隱情菩颖,我是刑警寧澤,帶...
    沈念sama閱讀 35,477評(píng)論 5 345
  • 正文 年R本政府宣布为障,位于F島的核電站晦闰,受9級(jí)特大地震影響放祟,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜呻右,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,088評(píng)論 3 328
  • 文/蒙蒙 一跪妥、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧声滥,春花似錦眉撵、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至憾赁,卻和暖如春污朽,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背龙考。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工蟆肆, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人晦款。 一個(gè)月前我還...
    沈念sama閱讀 47,876評(píng)論 2 370
  • 正文 我出身青樓炎功,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親缓溅。 傳聞我的和親對(duì)象是個(gè)殘疾皇子蛇损,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,700評(píng)論 2 354

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