迭代器与生成器

一、迭代器的特征

迭代器有一个next()方法,每次调用时会返回一个对象,该对象的结构为{value:xxx,done:true},其中value表示下次应该返回的值,done表示是否还有值可提供。

当没有值可提供时,done为true,如果迭代器在迭代结束时使用了return xxx,则value为xxx,否则为undefined。

function createIterator(items) { 
    var i = 0;
     return { 
         next: function() { 
             var done = (i >= items.length); 
             var value = !done ? items[i++] : undefined; 
             return { done: done, value: value }; 
        } 
    }; 
} 
var iterator = createIterator([1, 2, 3]); 
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: undefined, done: true }" 
// 之后的所有调用 
console.log(iterator.next()); // "{ value: undefined, done: true }"

二、迭代器的作用

  • 用于依序访问可迭代的对象,可迭代的对象为:Array、Map、Set、String、TypedArray、函数的 arguments对象、NodeList 对象
  • 可迭代对象( iterable )内部有一个 Symbol.iterator 属性。这 个 Symbol.iterator 知名符号定义了为指定对象返回迭代器的函数。
  • 集合类型有三种迭代器:

             entries() :返回一个包含键值对的迭代器,此迭代器是Map类型默认的迭代器;Array把下标做为key;而Set则把值作为key

             values() :返回一个包含集合中的值的迭代器,此迭代器是Set与Array的默认迭代器

             keys() :返回一个包含集合中的键的迭代器。

let colors = [ "red", "green", "blue" ];
for (let entry of colors.entries()) { 
    console.log(entry); 
}
  • 当使用for of访问对象时,如果没有显示指定使用哪个迭代器,则调用每种类型默认的。
  • 在for of中可以使用break语句跳出for of,可使用throw new Error('xxx')终止程序继续运行

三、使用生成器创造迭代器

 在function后面加一个*号,然后在函数内部使用yield标识符指定调用next时应该返回的值

function *createIterator(items) { 
    for (let i = 0; i < items.length; i++) { 
        yield items[i]; 
    } 
} 
let iterator = createIterator([1, 2, 3]); 
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: undefined, done: true }" 
// 之后的所有调用 
console.log(iterator.next()); // "{ value: undefined, done: true }"

生成器一是特殊的函数,因此可以作为对象的方法

var o = { 
    *createIterator(items) { 
        for (let i = 0; i < items.length; i++) { 
            yield items[i]; 
        } 
    } 
}; 
let iterator = o.createIterator([1, 2, 3]);

yield关键字不能放在生成器内部的函数中,下面代码是错误的

function *createIterator(items) { 
    items.forEach(function(item) { 
        // 语法错误 
        yield item + 1; 
    }); 
}

四、把不可迭代的对象变成可迭代的

默认情况下,我们自己创建的对象是不可迭代的,可以自己定义一个生成器让对象变成可迭代对象

let collection = { 
    items: [], 
    *[Symbol.iterator]() { 
        for (let item of this.items) { 
            yield item; 
        } 
    } 
}; 
collection.items.push(1); 
collection.items.push(2);
collection.items.push(3); 
for (let x of collection) { 
    console.log(x); 
}

 五、迭代器的next和生成器的yield进行通讯

1. 在生成器中,当下一个yield的值依赖上一个yield的结果进行计算时,可以使用迭代器的next(xxx)方法传值来覆盖上一个yield的结果,例子

function *createIterator() {
 let first = yield 1;
 let second = yield first + 2; // 4 + 2
 yield 3; 
}
let iterator = createIterator();
console.log(iterator.next());  // 第1个next传值不会生效,因为没有上一个yield
console.log(iterator.next(4)); // first变成了4,结果为"{ value: 6, done: false }"
console.log(iterator.next(5)); // 第3条yield不依赖上一条yield的结果进行计算,传值无效
console.log(iterator.next()); // "{ value: undefined, done: true }"

 2.通过iterator.throw()向生成器传递错误来阻止后续的迭代

function *createIterator() {
 let first = yield 1;
 let second = yield first + 2; // 4 + 2
 yield 3; 
}
let iterator = createIterator();
console.log(iterator.next());  // 第1个next传值不会生效,因为没有上一个yield
console.log(iterator.throw('后面的别运行了'));
console.log(iterator.next(5)); // 不会执行
console.log(iterator.next()); //  不会执行

 3.在生成器中通过try catch屏蔽错误

下面的代码在第二个next前抛出了错误,也就是生成器在执行第一条yield时会出现错误,采用try catch处理后,生成器可以继续向下运行

function *createIterator() {
 let first;
 try{
    first = yield 1;
 }
 catch(ex){
     first=100
 }
 let second = yield first + 2; // 4 + 2
 yield 3; 
}
let iterator = createIterator();
console.log(iterator.next());
console.log(iterator.throw('后面的别运行了')); //{value: 102, done: false}
console.log(iterator.next(5)); //没有依赖上一个yield的结果进行计算,返回 {value: 3, done: false}
console.log(iterator.next()); // {value: undefined, done: true}

 六、生成器合并

1. 可以将多个生成器合并成一个,方法是新写一个生成器函数,然后把生成器内部的各个yield分别指向不同的生成器。

合并后程序是按顺序执行的,即先把第一个生成器内的所有yield执行完再执行下一个。

function *createOne() {
   for(let i=4;i<7;i++){
       yield i
   }
}
function *createTwo() {
   for(let i=7;i<10;i++){
       yield i
   }
}
function *createAll(){
    yield *createOne();
    yield *createTwo();
}
var iterator=createAll();
for(let i of iterator){
    console.log(i)
}

2.后一个生成器依赖前一个生成器的结果

可以在前一个迭代器中使用return返回一个迭代结束后的值,然后把该值传给第二个迭代器的yield使用

function *createOne() {
   for(let i=4;i<7;i++){
       yield i
   }
   return 101;
}
function *createTwo(oneResult) {
   for(let i=7;i<10;i++){
       yield i+oneResult
   }
}
function *createAll(){
    let result=yield *createOne();
    yield *createTwo(result);
}
var iterator=createAll();
for(let i of iterator){
    console.log(i)
}

 七、任务执行器

每次调用next()时都会执行yield上面的语句,还有一条yield后面的语句。

function* gen(){
    console.log('one')
    yield 1;
    console.log('b')
    yield 2;
    yield 3;
}
var t=gen();
console.log(t.next());
console.log(t.next());

 那么我们能不能做一个任务运行器自动执行next呢?答案是使用递归

let run=function*(generator){
    let task=generator();
    let result=task.next();启动任务运行器
    function autoNext(){
        if(!task.done){
            result=task.next(result.value)
            step();//递归执行下一个next()
        }
    }
    autoNext();
}
run(function*() {
    let value = yield 1;
    console.log(value); // 1
    value = yield value + 3;
    console.log(value); // 4
});
原文地址:https://www.cnblogs.com/94pm/p/9653152.html