博客 / 詳情

返回

【js】迭代器與生成器

迭代器(Iterator)

有時也稱為遍歷器 - 迭代器對象
作用

  1. 為各種數據結構提供統一的訪問接口
  2. 使數據結構的成員按照某種次序排列
  3. 統一的迭代方式for...of循環

1.可迭代(iterable)

iterable:擁有Symbol.iterator屬性的數據結構是可迭代的
Symbol.iterator:值為迭代器生成函數
原生可迭代的數據結構

  1. Array
  2. Map
  3. Set
  4. String
  5. TypedArray
  6. arguments
  7. NodeList

    這些數據結構均可以使用for...of來遍歷
//以數組為例,判斷是否有Symbol.iterator屬性
const arr = [1, 2, 3, 4]
Symbol.iterator in arr //true
//for...of遍歷數組
for(const i of arr){
    console.log(i) //1 2 3 4
}

2.迭代器生成函數(Symbole.iterator)

返回一個迭代器對象

const arr = [1,2,3,4]
console.log(arr[Symbol.iterator]())

image.png

迭代器中有一個next函數
next()會返回一個成員對象,有value和done兩個屬性,value表示當前成員的值,done表示遍歷是否終止
for...of每次的遍歷都會調用next函數並取value

2.1next()方法

const arr = [1, 2, 3, 4]
let iter = arr[Symbol.iterator]() //返回一個遍歷器對象
console.log(iter.next());//{value: 1, done: false}
console.log(iter.next());//{value: 2, done: false}
console.log(iter.next());//{value: 3, done: false}
console.log(iter.next());//{value: 4, done: false}
console.log(iter.next());//{value: undefined, done: true}
遍歷器對象可以視為一個指針,每次調用上面的next方法都會移動指針

2.2實現一個迭代器生成函數

數組

//實現
function makeIterator(arr) {
    var nextIndex = 0;
    return {
        next() {
            return nextIndex < arr.length ?
                { value: arr[nextIndex++], done: false } :
                { value: undefined, done: true }
        }
    }
}
//測試
const arr = [1]
let iter = makeIterator(arr)
console.log(iter.next()) //{value: 1, done: false}
console.log(iter.next());//{value: undefined, done: true}
  • 對於對象也可以去實現一個迭代器生成函數,思路大致一樣
  • 但是對象上有豐富的方法間接生成一個數組,Object.values(obj)、keys、entries,這樣就間接擁有了Symbol.iterator接口,可以使用for...of循環遍歷
  • 對於對象這種非線性的數據結構,部署迭代器接口相當於將其轉化為線性結構,不如直接使用Map

生成器(Generator)

  • 從語法和結構上看,封裝了多個內部狀態 - 狀態機
  • 從返回結果看,返回了一個迭代器對象 - 迭代器生成函數

形式

  1. functon與函數名之間有一個星號*
  2. 內部使用yield語句定義不同的內部狀態

    function* generator(){
     yield 1;
     yield 2;
     return 3;
    }

1.next方法

1.1調用

調用生成器函數不會運行內部代碼,會返回一個迭代器對象,上面有next方法

function* generator() {
    console.log("a");
    yield 1;
    console.log("b");
    return 2
    console.log("c");
    yield 3
}
let iter = generator()
console.log(iter);

image.png

console.log(iter.next());// "a" {value:1,done:false}
console.log(iter.next());// "b" {value:2,done:true}
console.log(iter.next());// {value:undefined,done:true}
第一次調用next會開始運行函數,從起始到第一個yield結束,返回yield後的狀態
再調用next時會從上一個yield語句後開始執行,直到遇到下一個yield
當執行到return時,返回的對象done屬性為true,且後續代碼不再執行,下一調用返回{value:undefined,done:true}
yield不會終止只會中斷且返回的done始終是false

1.2參數

next方法中可以添加參數,作為上一個yield的值,yield本身沒有值

function* generator(x) {
    var y = 2 * (yield (x + 1));
    var z = yield (y / 3);
    return (x + y + z)
}
let iter = generator(5)
console.log(iter.next());//{value: 6, done: false}
console.log(iter.next(12));//{value: 8, done: false}
console.log(iter.next(13));//{value: 42, done: true}
第一次返回結果為 x + 1 => 5 + 1
第二次的返回結果y由第一個yield計算而來,其值為第二個next()中的參數 2*12/3 = 8
z的值由第二個yield的值,其值為第三個next()中的參數 5 + 24 + 13 = 42
function* generator() {
    let val1 = yield 1
    console.log(""val1);
    let val2 = yield 2
    console.log(val2);
}
let iter = generator()
iter.next("one")
iter.next("two") //第一個yield的值:two
iter.next("three")//第二個yield的值:three

2.yield語句

  • 普通的yield語句可以中斷執行,並且返回轉態
  • yield* 後可以跟隨一個迭代器對象,相當於在生成器內調用另一個生成器

    function* generator1() {
      yield 1
      yield 2
    }
    function* generator2() {
      yield 'a'
      yield* generator1()
      yield 'b'
      return 'c'
    }
    let gene2 = generator2()
    for (const i of gene2) {
      console.log(i)// 'a' 1 2 'b'
    }
    在一個生產器yield* 執行另一個生成器,相當於將它的語句插入到該生成器中
    return返回的done是true,for...of在遍歷時不會返回其值
  • 如果是yield後執行一個生成器,next返回的value是一個迭代器對象
function* generator1() {
    yield 1
    yield 2
}
function* generator2() {
    yield 'a'
    yield generator1()
}
let gene2 = generator2()
console.log(gene2.next());
console.log(gene2.next());

image.png

3.Generator.prototype.throw()

3.1作用

外部拋出錯誤,在內部捕獲

function* generator() {
    try {
        yield 1
        yield 2
        yield 3
    }
    catch (e) {
        console.log("內部捕獲:" + e);
    }
}
let gene = generator()
try {
    console.log(gene.next());
    gene.throw("第一次錯誤");
    console.log(gene.next());
    gene.throw("第二次錯誤");
    console.log(gene.next());
     gene.throw("第三次錯誤");
    console.log(gene.next());
} catch (e) {
    console.log("外部捕獲:" + e);
}

image.png

第一次調用throw會在內部的catch中捕獲,try中的語句不再執行
出現錯誤後再調用next為終止狀態的對象,説明生成器被終止了
由於生成器被終止,第二次錯誤會在外部捕獲,生成器徹底終止,再調用上面的方法不再生效

3.2返回值

throw返回下一個next的狀態

function* generator() {
    try {
        console.log(1);
        yield "in11"
        console.log(2);
        yield "in12"
    }
    catch (e) {
        console.log("內部捕獲1");
    }

    console.log(3);
    yield "out1"

    try {
        console.log(4);
        yield "in21"
        console.log(5);
        yield "in22"
    }
    catch (e) {
        console.log("內部捕獲2");
    }

    console.log(6);
    yield "out2"
    yield "out3"
}
let gene = generator()
try {
    console.log(gene.next()); //1 {value: 'in11', done: false}
    console.log(gene.throw());//內部捕獲1 3 {value: 'out1', done: false}
    console.log(gene.next());//4 {value: 'in21', done: false}
    console.log(gene.throw());//內部捕獲2 6 {value: 'out2', done: false}
    console.log(gene.next());//7 {value: 'out3', done: false}
    console.log(gene.throw());//外部捕獲
    console.log(gene.next());
} catch (e) {
    console.log("外部捕獲");
}

上面的代碼中有多個try catch
第一個next會進入第一個try中
第一個throw的調用環境在有catch,跳出try執行至下一個yield
下一個yield有catch可繼續throw,若沒有catch則在外部捕獲生成器終止

throw在錯誤被捕獲再去返回下一個next的返回值

4.Generator.prototype.return()

返回給定的值,終止生成器

function* generator(){
    yield 1
    yield 2
    yield 3
}
let gene = generator()
gene.next() // {value:1,done:false}
gene.return("foo")// {value:"foo",done:false}
gene.next() // {value:undefined,done:false}
  • 如果在try..finally中,return會在finally後運行

    function* generator() {
      try {
          yield 1
          yield 2
      } finally {
          yield 3
          yield 4
      }
      yield 5
    }
    let gene = generator()
    console.log(gene.next());//{value: 1, done: false}
    console.log(gene.return(6));//{value: 3, done: false}
    console.log(gene.next());//{value: 4, done: false}
    console.log(gene.next());//{value: 6, done: true}
    console.log(gene.next());//{value: undefined, done: true}
    第一個next進入try中,如果直接return會直接結束
    在try中執行return,會進入finally中,執行至第一個yield,返回狀態,並將return添加到finally的結尾
    在finally中執行return會直接結束

5.部署一個Iterator接口

對象

function* iterEntries(obj) {
    let keys = Object.keys(obj)
    for (let i = 0; i < keys.length; i++) {
        let key = keys[i];
        yield [key, obj[key]]
    }
}
let objEntr = iterEntries({ a: 1, b: 2, c: 3 })
for (const [key, val] of objEntr) {
    console.log(key, val);
}
'a' 1
'b' 2
'c' 3
user avatar niumingxin 頭像 qianduanlangzi_5881b7a7d77f0 頭像 pangsir8983 頭像 gfeteam 頭像 tofrankie 頭像 qingji_58b3c385d0028 頭像 jidongdemogu 頭像 zpfei 頭像 anetin 頭像 mall4j 頭像 mi2nagemao 頭像 nqbefgvs 頭像
16 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.