一、迭代器的特征
迭代器有一个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 });