【深入淺出ES6】Iterator/Generator

Eric_zhang發表於2019-03-04

【深入淺出ES6】Iterator/Generator

迭代器與生成器

背景

傳統的遍歷迭代集合的方式使用for迴圈,ES6引入了迭代器物件,使得操作集合更加便捷,Set、Map、for...of、擴充套件運算子 都用到了迭代器,注:普通的物件不是可迭代的,因為沒有Symbol(Symbol.iterator)屬性

迭代器Iterator

迭代器表示可以被遍歷迭代的物件,以程式設計的方式返回集合中的下一項,迭代器物件都擁有next()方法,返回{value:'',done:bool},value表示當前迭代位置的返回值,done表示是否迭代結束,結束時其值為false

生成器Generator

是一個返回迭代器物件的函式

  1. 使用 function *函式名(){} 來建立生成器
  2. 使用生成器函式表示式建立生成器:let iterator = function *(){} 注:不能使用箭頭函式建立生成器
  3. 函式體中使用yield關鍵字定義每次迭代器呼叫next()返回的value結果,yield指令相當於起到暫停程式碼執行的作用,只有在迭代器呼叫next()時,才會觸發下一個yield繼續執行
  4. 迭代完yield後,下一次迭代會返回return的值;如果return放在中間,則迭代到return行後,後面的yield也不會執行了
function *createIterator(){
    yield 1;
    yield 2;
    yield 3;
    return 123
}
let iterator = createIterator()
console.log(iterator.next()) //{value: 1, done: false}
console.log(iterator.next()) //{value: 2, done: false}
console.log(iterator.next()) //{value: 3, done: false}
console.log(iterator.next()) //{value: 123, done: true}
console.log(iterator.next()) //{value: undefined, done: true}
複製程式碼

可迭代物件與for...of

可迭代物件(iterable)是指帶Symbol(Symbol.iterator)屬性的物件,Symbol(Symbol.iterator) 本質上是一個生成器函式,所有的集合物件(陣列、Set、Map)及字串的原型上都預設攜帶這個屬性,因此都是可迭代物件

【深入淺出ES6】Iterator/Generator

可迭代物件可以使用ES6新增的for-of方法遍歷

for-of的優勢:使用傳統for迴圈時,需要通過邏輯控制遍歷條件,特別是多級巢狀時會導致複雜度增加,容易出錯 for-of迴圈的原理:

  1. 首先內部會自動呼叫集合的Symbol.iterator方法,獲取到預設的迭代器物件
  2. 然後在每次迴圈時,都會自動調迭代器的next(),將返回結果物件的value值賦給臨時變數上(迭代器返回的結果物件的value就是可迭代物件上定義的值)
  3. 下一輪迴圈繼續呼叫next(),直到遇到返回的結果物件的done屬性值為true,停止遍歷
    let arr = [1,2,3]
    let iterator = arr[Symbol.iterator]()
    console.log(iterator.next()) //{value: 1, done: false}
    console.log(iterator.next()) //{value: 2, done: false}
    console.log(iterator.next()) //{value: 3, done: false}
    複製程式碼

建立可迭代物件

因為我們正常建立的物件是沒有Symbol.iterator屬性的,所以是不可迭代的。我們可以自定義一個Symbol.iterator屬性

let obj = {
    items:[],
    *[Symbol.iterator](){ //使用Symbol型別要使用[]
        for(let item of this.items){
            yield item
        }
    }
}
obj.items.push(1)
obj.items.push(2)
obj.items.push(3)
console.log(obj) // {items: Array(3), Symbol(Symbol.iterator): ƒ}
for(let value of obj){
    console.log(value) //1,2,3
}
複製程式碼

集合內建的迭代器

ES6具有內建迭代器的集合物件型別:陣列、Set、Map

  • entries():返回一個包含鍵值對的迭代器,迭代返回的結果物件value是鍵值對陣列
  • values():返回一個包含集合中的值的迭代器
  • keys():返回一個包含集合中的鍵的迭代器

在for-of中使用上面三種的其中一種都可以,下面是顯式指定迭代器的寫法:

let colors = ['red', 'green', 'blue']
let tracking = new Set([123,456,789])
let data = new Map()

data.set('title','understanding ES6')
data.set('format', 'ebook')

for(let entry of colors.entries()){
    console.log(entry)
}
for(let entry of tracking.entries()){
    console.log(entry)
}
for(let entry of data.entries()){
    console.log(entry)
}
/*
[0, "red"]
[1, "green"]
[2, "blue"]
[123, 123]
[456, 456]
[789, 789]
["title", "understanding ES6"]
["format", "ebook"]
*/

for(let value of colors.values()){
    console.log(value)
}
for(let value of tracking.values()){
    console.log(value)
}
for(let value of data.values()){
    console.log(value)
}
/*
red
green
blue
123
456
789
understanding ES6
ebook
*/
for(let key of colors.keys()){
    console.log(key)
}
for(let key of tracking.keys()){
    console.log(key)
}
for(let key of data.keys()){
    console.log(key)
}
/*
0
1
2
123
456
789
title
format
*/
複製程式碼

三種幾個型別預設的迭代器是什麼呢?

當都有for-of迴圈沒有顯式指定迭代器時,每種集合型別都有一個預設的迭代器供迴圈呼叫

  • values():陣列與Set的預設迭代器
  • entries():Map的預設迭代器
let colors = ['red', 'green', 'blue']
let tracking = new Set([123,456,789])
let data = new Map()

data.set('title','understanding ES6')
data.set('format', 'ebook')

//與使用colors.values()相同
for(let value of colors){
    console.log(value)
}
/*
red
green
blue
*/

//與使用tracking.values()相同
for(let value of tracking){
    console.log(value)
}
/*
123
456
789
*/

//與使用data.entries()相同
//使用了陣列解構,方便操作迭代器返回的結果物件[0, "red"]格式
for(let [key,value] of data){
    console.log(key + ' ' + value)
}
/*
title understanding ES6
format ebook
*/
複製程式碼

NodeList的迭代器

使用document.getElementsByXXX獲取的文件物件模型(DOM)是一種NodeList型別,其比較大的特點是,有類似陣列的length屬性,同時具有預設迭代器,因此NodeList可以使用for-of迭代

擴充套件運算子

擴充套件運算子可以應用到任何可迭代的集合上,擴充套件運算子會呼叫預設的迭代器,返回相應的結果物件value值

生成器委託:可以將多個生成器合併到一起

function *numberIterator(){
    yield 1;
    yield 2;
}
function *colorIterator(){
    yield 'red';
    yield 'blue';
}
function *combinedInterator(){
    yield *numberIterator();
    yield *colorIterator();
    yield true
}

let interator = combinedInterator()
console.log(interator.next()) //{value: 1, done: false}
console.log(interator.next()) //{value: 2, done: false}
console.log(interator.next()) //{value: "red", done: false}
console.log(interator.next()) //{value: "blue", done: false}
console.log(interator.next()) //{value: true, done: false}
console.log(interator.next()) //{value: undefined, done: true}
複製程式碼

傳遞引數給迭代器

function * createIterator(){
    let first = yield 1;
    console.log('first',first)
    let second = yield first *3;
    console.log('second',second)
    yield second * 5
}
let iterator = createIterator()

console.log(iterator.next(3)) //第一個next()中的引數不起作用
console.log(iterator.next(2))
console.log(iterator.next(4))
console.log(iterator.next())
/*
{value: 1, done: false}
first 2
{value: 6, done: false}
second 4
{value: 20, done: false}
{value: undefined, done: true}
*/
複製程式碼
  1. 執行第一個next(),第一個next()比較特殊,傳進來的引數被忽略
  2. 執行完第一個'yield 1'後,程式碼被暫停
  3. 執行第二個next(),next()中傳遞的引數2作為'let first = yield 1'中的返回值,賦值給first變數,所以執行'yield first *3'的結果是2x3=6
  4. 執行第三個next(),next()中傳遞的引數4作為second的值,以此類推

使用生成器、迭代器實現非同步任務執行

執行非同步任務的傳統方法是建立一個帶有回撥的函式,可以很完美的解決數量比較少的任務;但是如果有多層巢狀回撥,或者按順序執行一系列任務時,使用生成器和迭代器可以完成非同步任務的實現,yield指令可以起到很好暫停程式碼執行的作用

  1. 建立一個呼叫生成器並啟動迭代器的函式
  2. 呼叫該函式,傳入期望執行任務的生成器
    function run(taskDef){
        //呼叫生成器,得到迭代器
        let task = taskDef()
         //啟動任務
        let result = task.next()
        // 使用遞迴 保證迭代器的呼叫
        function step(){
            if(!result.done){
                result = task.next()
                step()
            }
        }
        //開始處理
        step()
    }
    
     run(function*(){
         console.log(1);
         yield;
         console.log(2);
         yield;
         console.log(3);
     })
     /*
     1
     2
     3
     */
    複製程式碼

相關文章