主要知识点: 迭代器, 生成器, 可迭代对象以及 for-of 循环, 迭代器的高级功能以及创建异步任务处理器
迭代器与生成器. png
1. 迭代器
何为迭代器?
迭代器是被设计专用于迭代的对象, 带有特定接口. 所有的迭代器对象都拥有 next() 方
法, 会返回一个结果对象. 该结果对象有两个属性: 对应下一个值的 value , 以及一个布尔
类型的 done , 其值为 true 时表示没有更多值可供使用. 迭代器持有一个指向集合位置的
内部指针, 每当调用了 next() 方法, 迭代器就会返回相应的下一个值.
2. 生成器
何为生成器?
生成器(generator ) 是能返回一个迭代器的函数. 生成器函数由放在 function 关键字之后的一个星号( * ) 来表示, 并能使用新的 yield 关键字. 将星号紧跟在 function 关键字之后, 或是在中间留出空格, 都是没问题的. 例如:
- function*generator(){
- yield 1;
- yield 2;
- yield 3;
- }
- let iterator = generator();
- console.log(iterator.next().value);//1
- console.log(iterator.next().value);//2
生成器函数最有意思的地方是它们会在每一个 yield 语句后停止, 例如在上面的代码中执行 yield 1 后, 该函数不会在继续往下执行. 等待下一次调用 next()后, 才会继续往下执行 yield 2.
除了使用函数声明的方式创建一个生成器外, 还可以使用函数表达式来创建一个生成器. 由于生成器就是一个函数, 同样可以使用对象字面量的方式, 将对象的属性赋值为一个生成器函数.
3. 可迭代对象与 for-of 循环
可迭代对象是包含 Symbol.iterator 属性的对象, 这个 Symbol.iterator 属性对应着能够返回该对象的迭代器的函数. 在 ES6 中, 所有的集合对象 (数组, Set 和 Map) 以及字符串都是可迭代对象, 因此它们都被指定了默认的迭代器. 可迭代对象可以与 ES6 中新增的 for-of 循环配合使用.
迭代器解决了 for 循环中追踪索引的问题, 而 for-of 循环, 则是完全删除追踪集合索引的需要, 更能专注于操作集合内容. for-of 循环在循环每次执行时会调用可迭代对象的 next()方法, 并将结果对象的 value 值存储在一个变量上, 循环过程直到结果对象 done 属性变成 true 为止:
- let arr = [1,2,3];
- for(let num of arr){
- console.log(num);
- }
输出结果为: 1,2,3
for-of 循环首先会调用 arr 数组中 Symbol.iterator 属性对象的函数, 就会获取到该数组对应的迭代器, 接下来 iterator.next()被调用, 迭代器结果对象的 value 属性会被放入到变量 num 中. 数组中的数据项会依次存入到变量 num 中, 直到迭代器结果对象中的 done 属性变成 true 为止, 循环就结束.
访问可迭代对象的默认迭代器
可以使用可迭代对象的 Symbol.iterator 来访问对象上可返回迭代器的函数:
- let arr = [1,2,3];
- // 访问默认迭代器
- let iterator = arr[Symbol.iterator]();
- console.log(iterator.next().value); //1
- console.log(iterator.next().value); //2
通过 Symbol.iterator 属性获取到该对象的可返回迭代器的函数, 然后执行该函数得到对象的可迭代器. 同样的, 可是使用 Symbol.iterator 属性来检查对象是否是可迭代对象.
创建可迭代对象
数组, Set 等集合对象是默认的迭代器, 当然也可以为对象创建自定义的迭代器, 使其成为可迭代对象. 那么迭代器如何生成? 我们已经知道, 生成器就是一个可以返回迭代器的函数, 因此自定义迭代器, 就是写一个生成器函数. 同时, 可迭代对象必须具有 Symbol.iterator 属性, 并且该属性对应着一个能够返回迭代器的函数, 因此只需要将这个生成器函数赋值给 Symbol.iterator 属性即可:
- // 创建可迭代对象
- let obj = {
- items:[],
- *[Symbol.iterator](){
- for(let item of this.items){
- yield item;
- }
- }
- }
- obj.items.push(1);
- obj.items.push(2);
- for(let num of obj){
- console.log(num);
- }
输出: 1,2
4. 内置的迭代器
ES6 中许多内置类型已经包含了默认的迭代器, 只有当默认迭代器满足不了时, 才会创建自定义的迭代器. 如果新建对象时, 要想把该对象转换成可迭代对象的话, 一般才会需要自定义迭代器.
集合迭代器
ES6 中有三种集合对象: 数组, Map 和 Set, 这三种类型都拥有默认的迭代器:
entries(): 返回一个包含键值对的迭代器;
values(): 返回一个包含集合中的值的迭代器;
keys(): 返回一个包含集合中的键的迭代器;
调用 entries()迭代器会在每次调用 next()方法返回一个双项数组, 此数组代表集合数据项中的键和值: 对于数组来说, 第一项是数组索引; 对于 Set 来说, 第一项是值(因为 Set 的键和值相同), 对于 Map 来说, 就是键值对的值;
values()迭代器能够返回集合中的每一个值;
keys()迭代器能够返回集合中的每一个键;
集合的默认迭代器
当 for-of 循环没有显式指定迭代器时, 集合对象会有默认的迭代器. values()方法是数组和 Set 默认的迭代器, 而 entries()方法是 Map 默认迭代器.
字符串的迭代器
ES6 旨在为 Unicode 提供了完全支持, 字符串的默认迭代器就是解决字符串迭代问题的一种尝试, 这样一来, 借助字符串默认迭代器就能处理字符而非码元:
- // 字符串默认迭代器
- let str ='A B';
- for(let s of str){
- console.log(s); //A B
- }
扩展运算符与非数组的可迭代对象
扩展运算符能作用于所有可迭代对象, 并且会使用默认迭代器来判断需要哪些值. 在数组字面量中可以使用扩展运算符将可迭代对象填充到数组中:
- // 扩展运算符可作用到所有可迭代对象
- let arr = [1,2,3];
- let array = [...arr];
- console.log(array); [1,2,3]
并且, 可以不限次数在数组字面量中使用扩展运算符, 而且可以在任意位置用扩展运算符将可迭代对象填充到数组中:
- let arr = [1,2,3];
- let arr2 = [7,8,9];
- let array = [...arr,5,...arr2];
- console.log(array); //1,2,3,5,7,8,9
5. 迭代器高级功能
能够通过 next()方法向迭代器传递参数, 当一个参数传递给 next()方法时, 该参数就会成为生成器内部 yield 语句中的变量值.
- // 迭代器的高级功能
- function * generator(){
- let first = yield 1;
- let second = yield first+2;
- let third = yield second+3;
- }
- let iterator = generator();
- console.log(iterator.next()); //{value: 1, done: false}
- console.log(iterator.next(4)); //{value: 6, done: false}
- console.log(iterator.next(5)); //{value: 8, done: false}
- console.log(iterator.next()); //{value: undefined, done: true}
示例代码中, 当通过 next()方法传入参数时, 会赋值给 yield 语句中的变量.
在迭代器中抛出错误
能传递给迭代器的不仅是数据, 还可以是错误, 迭代器可以选择一个 throw()方法, 用于指示迭代器应在恢复执行时抛出一个错误:
- // 迭代器抛出错误
- function * generator(){
- let first = yield 1;
- let second = yield first+2;
- let third = yield second+3;
- }
- let iterator = generator();
- console.log(iterator.next()); //{value: 1, done: false}
- console.log(iterator.next(4)); //{value: 6, done: false}
- console.log(iterator.throw(new Error('Error!'))); //Uncaught Error: Error!
- console.log(iterator.next()); // 不会执行
在生成器中同样可以使用 try-catch 来捕捉错误:
- function * generator(){
- let first = yield 1;
- let second;
- try{
- second = yield first+2;
- }catch(ex){
- second = 6
- }
- let third = yield second+3;
- }
- let iterator = generator();
- console.log(iterator.next()); //{value: 1, done: false}
- console.log(iterator.next(4)); //{value: 6, done: false}
- console.log(iterator.throw(new Error('Error!'))); //{value: 9, done: false}
- console.log(iterator.next()); //{value: undefined, done: true}
生成器的 return 语句
由于生成器是函数, 你可以在它内部使用 return 语句, 既可以让生成器早一点退出执行, 也可以指定在 next() 方法最后一次调用时的返回值. 大多数情况, 迭代器上的
next() 的最后一次调用都返回了 undefined , 但你还可以像在其他函数中那样, 使用
return 来指定另一个返回值. 在生成器内, return 表明所有的处理已完成, 因此 done
属性会被设为 true , 而如果提供了返回值, 就会被用于 value 字段. 比如, 利用 return 让生成器更早的退出:
- function * gene(){
- yield 1;
- return;
- yield 2;
- yield 3;
- }
- let iterator = gene();
- console.log(iterator.next());//{value: 1, done: false}
- console.log(iterator.next());//{value: undefined, done: true}
- console.log(iterator.next());//{value: undefined, done: true}
由于使用 return 语句, 能够让生成器更早结束, 因此在第二次以及第三次调用 next()方法时, 返回结果对象为:
- {value: undefined, done: true}
- .
还可以使用 return 语句指定最后返回值:
- function * gene(){
- yield 1;
- return 'finish';
- }
- let iterator = gene();
- console.log(iterator.next());//{value: 1, done: false}
- console.log(iterator.next());//{value: "finish", done: true}
- console.log(iterator.next());//{value: undefined, done: true}
当第二次调用 next()方法时, 返回了设置的返回值: finish. 第三次调用 next() 返回了一个对象, 其 value 属性再次变回 undefined , 你在 return 语句中指定的任意值都只会在结果对象中出现一次, 此后 value 字段就会被重置为 undefined .
生成器委托
生成器委托是指: 将生成器组合起来使用, 构成一个生成器. 组合生成器的语法需要 yield 和 *,* 落在 yield 关键字与生成器函数名之间即可:
- function * gene1(){
- yield 'red';
- yield 'green';
- }
- function * gene2(){
- yield 1;
- yield 2;
- }
- function * combined(){
- yield * gene1();
- yield * gene2();
- }
- let iterator = combined();
- console.log(iterator.next());//{value: "red", done: false}
- console.log(iterator.next());//{value: "green", done: false}
- console.log(iterator.next());//{value: 1, done: false}
- console.log(iterator.next());//{value: 2, done: true}
- console.log(iterator.next());//{value: undefined, done: true}
此例中将生成器 gene1 和 gene2 组合而成生成器 combined, 每次调用 combined 的 next()方法时, 实际上会委托到具体的生成器中, 当 gene1 生成器中所有的 yield 执行完退出之后, 才会继续执行 gene2, 当 gene2 执行完退出之后, 也就意味着 combined 生成器执行结束.
在使用生成器委托组合新的生成器时, 前一个执行的生成器返回值可以作为下一个生成器的参数:
- // 利用生成器返回值
- function * gene1(){
- yield 1;
- return 2;
- }
- function * gene2(count){
- for(let i=0;i<count;i++){
- yield 'repeat';
- }
- }
- function * combined(){
- let result = yield * gene1();
- yield result;
- yield*gene2(result);
- }
- let iterator = combined();
- console.log(iterator.next());//{value: 1, done: false}
- console.log(iterator.next());//{value: 2, done: false}
- console.log(iterator.next());//{value: "repeat", done: false}
- console.log(iterator.next());//{value: "repeat", done: false}
- console.log(iterator.next());//{value: undefined, done: true}
此例中, 生成器 gene1 的返回值, 就作为了生成器 gene2 的参数.
6. 异步任务
一个简单的任务运行器
生成器函数中 yield 能暂停运行, 当再次调用 next()方法时才会重新往下运行. 一个简单的任务执行器, 就需要传入一个生成器函数, 然后每一次调用 next()方法就会 "一步步" 往下执行函数:
- // 任务执行器
- function run(taskDef) {
- // 创建迭代器, 让它在别处可用
- let task = taskDef();
- // 启动任务
- let result = task.next();
- // 递归使用函数来保持对 next() 的调用
- function step() {
- // 如果还有更多要做的
- if (!result.done) {
- result = task.next();
- step();
- }
- }
- // 开始处理过程
- step();
- }
- run(function*() {
- console.log(1);
- yield;
- console.log(2);
- yield;
- console.log(3);
- });
run() 函数接受一个任务定义(即一个生成器函数) 作为参数, 它会调用生成器来创建一个
迭代器, 并将迭代器存放在 task 变量上. 第一次对 next() 的调用启动
了迭代器, 并将结果存储下来以便稍后使用. step() 函数查看 result.done 是否为 false, 如果是就在递归调用自身之前调用 next() 方法. 每次调用 next() 都会把返回的结果保
存在 result 变量上, 它总是会被最新的信息所重写. 对于 step() 的初始调用启动了处理
过程, 该过程会查看 result.done 来判断是否还有更多要做的工作.
能够传递数据的任务运行器
如果需要传递数据的话, 也很容易, 也就是将上一次 yield 的值, 传递给下一次 next()调用即可, 仅仅只需要传送结果对象的 value 属性:
- // 任务执行器
- function run(taskDef) {
- // 创建迭代器, 让它在别处可用
- let task = taskDef();
- // 启动任务
- let result = task.next();
- // 递归使用函数来保持对 next() 的调用
- function step() {
- // 如果还有更多要做的
- if (!result.done) {
- result = task.next(result.value);
- console.log(result.value); //6 undefined
- step();
- }
- }
- // 开始处理过程
- step();
- }
- run(function*() {
- let value = yield 1;
- yield value+5;
- });
异步任务
上面的例子是简单的任务处理器, 甚至还是同步的. 实现任务器也主要是迭代器在每一次调用 next()方法时彼此间传递静态参数. 如果要将上面的任务处理器改装成异步任务处理器的话, 就需要 yield 能够返回一个能够执行回调函数的函数, 并且回调参数为该函数的参数即可.
什么是有回调函数的函数?
有这样的示例代码:
- function fetchData(callback) {
- return function(callback) {
- callback(null, "Hi!");
- };
- }
函数 fetchData 返回的是一个函数, 并且所返回的函数能够接受一个函数 callback. 当执行返回的函数时, 实际上是调用回调函数 callback. 但目前而言, 回调函数 callback 还是同步的, 可以改造成异步函数:
- function fetchData(callback) {
- return function(callback) {
- setTimeout(function() {
- callback(null, "Hi!");
- }, 50);
- };
- }
一个简单的异步任务处理器:
- // 异步任务处理器
- function run(taskDef){
- // 执行生成器, 创建迭代器
- let task = taskDef();
- // 启动任务
- let result = task.next();
- function step(){
- while(!result.done){
- if(typeof(result.value)==='function' ){
- result.value(()=>{
- console.log('hello world');
- })
- }
- result = task.next();
- step();
- }
- }
- step();
- }
- run(function *(){
- // 返回一个能够返回执行回调函数的函数, 并且回调函数还是该
- // 函数的参数
- yield function(callback){
- setTimeout(callback,3000);
- }
- });
上面的示例代码就是一个简单的异步任务处理器, 有这样几点要点:
使用生成器构造迭代器, 所以在 run 方法中传入的是生成器函数;
生成器函数中 yield 关键字, 返回的是一个能够执行回调函数的函数, 并且回调函数是该函数的一个参数;
7. 总结
使用迭代器可以用来遍历集合对象包含的数据, 调用迭代器的 next()方法可以返回一个结果对象, 其中 value 属性代表值, done 属性用来表示集合对象是否已经到了最后一项, 如果集合对象的值全部遍历完后, done 属性为 true;
Symbol.iterator 属性被用于定义对象的默认迭代器, 使用该属性可以为对象自定义迭代器. 当 Symbol.iterator 属性存在时, 该对象可以被认为是可迭代对象;
可迭代对象可以使用 for-of 循环, for-of 循环不需要关注集合对象的索引, 更能专注于对内容的处理;
数组, Set,Map 以及字符串都具有默认的迭代器;
扩展运算符可以作用于任何可迭代对象, 让可迭代对象转换成数组, 并且扩展运算符可以用于数组字面量中任何位置中, 让可迭代对象的数据项一次填入到新数组中;
生成器是一个特殊的函数, 语法上使用了 *,yield 能够返回结果, 并能暂停继续往下执行, 直到调用 next()方法后, 才能继续往下执行. 使用生成器委托能够将两个生成器合并组合成一个生成器;
能够使用生成器构造异步任务处理器;
来源: http://www.jianshu.com/p/9a0e03efacd7