本文是 重温基础 系列文章的第十三篇。
今日感受:每次自我年终总结,都会有各种情绪和收获。

本章节复习的是JS中的迭代器和生成器,常常用来处理集合。

前置知识:
JavaScrip已经提供多个迭代集合的方法,从简单的for循环到map()filter()
迭代器和生成器将迭代的概念直接带入核心语言,并提供一种机制来自定义for...of循环的行为。

本文会将知识点分为两大部分,简单介绍和详细介绍
简单介绍,适合基础入门会使用的目标;
详细介绍,会更加深入的做介绍,适合理解原理;

1. 概述

当我们使用循环语句迭代数据时,需初始化一个变量来记录每一次迭代在数据集合中的位置:

let a = ["aaa","bbb","ccc"];
for (let i = 0; i< a.length; i++){console.log(a[i]);
}

这边的i就是我们用来记录迭代位置的变量,但是在ES6开始,JavaScrip引入了迭代器这个特性,并且新的数组方法新的集合类型(如Set集合Map集合)都依赖迭代器的实现,这个新特性对于高效的数据处理而言是不可或缺的,在语言的其他特性中也都有迭代器的身影:新的for-of循环、展开运算符(...),甚至连异步编程都可以使用迭代器。

本文主要会介绍ES6中新增的迭代器(Iterator)和生成器(Generator)。

2. 迭代器(简单介绍)

迭代器是一种特殊对象,它具有一些专门为迭代过程设计的专有接口,所有的迭代器对象都有一个next()方法,每次调用都会返回一个结果对象。
这个结果对象,有两个属性: value: 表示下一个将要返回的值。 done: 一个布尔值,若没有更多可返回的数据时,值为true,否则false

如果最后一个值返回后,再调用next(),则返回的对象的done值为true,而value值如果没有值的话,返回的为undefined

ES5实现一个迭代器:

function myIterator(list){var i = 0;return {next: function(){var done = i >= list.length;var value = !done ? list[i++] : undefined;return {done : done,value : value}}}
}var iterator = myIterator([1,2,3]);
iterator.next();  // "{done: false, value: 1}"
iterator.next();  // "{done: false, value: 2}"
iterator.next();  // "{done: false, value: 3}"
iterator.next();  // "{done: true, value: undefined}"
// 以后的调用都一样
iterator.next();  // "{done: true, value: undefined}"

从上面代码可以看出,ES5的实现还是比较麻烦,而ES6新增的生成器,可以使得创建迭代器对象的过程更加简单。

3. 生成器(简单介绍)

生成器是一种返回迭代器的函数,通过function关键字后的星号(*)来表示,函数中会用到新的关键字yield。星号可以紧挨着function关键字,也可以在中间添加一个空格。

function *myIterator(){yield 1;yield 2;yield 3;
}
let iterator = myIterator();
iterator.next();  // "{done: false, value: 1}"
iterator.next();  // "{done: false, value: 2}"
iterator.next();  // "{done: false, value: 3}"
iterator.next();  // "{done: true, value: undefined}"
// 以后的调用都一样
iterator.next();  // "{done: true, value: undefined}"

生成器函数最有趣的部分是,每当执行完一条yield语句后函数就会自动停止执行,比如上面代码,当yield 1;执行完后,便不会执行任何语句,而是等到再调用迭代器的next()方法才会执行下一个语句,即yield 2;.
使用yield关键字可以返回任何值和表达式,因为可以通过生成器函数批量给迭代器添加元素:

function *myIterator(list){for(let  i = 0; i< list.length ; i ++){yield list[i];}
}var iterator = myIterator([1,2,3]);
iterator.next();  // "{done: false, value: 1}"
iterator.next();  // "{done: false, value: 2}"
iterator.next();  // "{done: false, value: 3}"
iterator.next();  // "{done: true, value: undefined}"
// 以后的调用都一样
iterator.next();  // "{done: true, value: undefined}"

生成器的适用返回很广,可以将它用于所有支持函数使用的地方。

4. 迭代器(详细介绍)

4.1 Iterator迭代器概念

Iterator是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成迭代操作(即依次处理该数据结构的所有成员)。

Iterator三个作用为各种数据结构,提供一个统一的、简便的访问接口;
使得数据结构的成员能够按某种次序排列;
* Iterator 接口主要供ES6新增的for...of消费;

4.2 Iterator迭代过程

  1. 创建一个指针对象,指向当前数据结构的起始位置。也就是说,迭代器对象本质上,就是一个指针对象。
  2. 第一次调用指针对象的next方法,可以将指针指向数据结构的第一个成员。
  3. 第二次调用指针对象的next方法,指针就指向数据结构的第二个成员。
  4. 不断调用指针对象的next方法,直到它指向数据结构的结束位置。

每一次调用next方法,都会返回数据结构的当前成员的信息。具体来说,就是返回一个包含valuedone两个属性的对象。

  • value属性是当前成员的值;
  • done属性是一个布尔值,表示迭代是否结束;

模拟next方法返回值:

let f = function (arr){var nextIndex = 0;return {next:function(){return nextIndex < arr.length ?{value: arr[nextIndex++], done: false}:{value: undefined, done: true}}}
}let a = f(['a', 'b']);
a.next(); // { value: "a", done: false }
a.next(); // { value: "b", done: false }
a.next(); // { value: undefined, done: true }

4.3 默认Iterator接口

若数据可迭代,即一种数据部署了Iterator接口。
ES6中默认的Iterator接口部署在数据结构的Symbol.iterator属性,即如果一个数据结构具有Symbol.iterator属性,就可以认为是可迭代Symbol.iterator属性本身是函数,是当前数据结构默认的迭代器生成函数。执行这个函数,就会返回一个迭代器。至于属性名Symbol.iterator,它是一个表达式,返回Symbol对象的iterator属性,这是一个预定义好的、类型为 Symbol 的特殊值,所以要放在方括号内(参见《Symbol》一章)。

原生具有Iterator接口的数据结构有Array Map Set String TypedArray 函数的 arguments 对象 * NodeList 对象

4.4 Iterator使用场景

  • (1)解构赋值
    对数组和 Set 结构进行解构赋值时,会默认调用Symbol.iterator方法。
let a = new Set().add('a').add('b').add('c');
let [x, y] = a;       // x = 'a'  y = 'b'
let [a1, ...a2] = a;  // a1 = 'a' a2 = ['b','c']

  • (2)扩展运算符
    扩展运算符(...)也会调用默认的 Iterator 接口。
let a = 'hello';
[...a];            //  ['h','e','l','l','o']let a = ['b', 'c'];
['a', ...a, 'd'];  // ['a', 'b', 'c', 'd']

  • (2)yield*yield*后面跟的是一个可迭代的结构,它会调用该结构的迭代器接口。
let a = function*(){yield 1;yield* [2,3,4];yield 5;
}let b = a();
b.next() // { value: 1, done: false }
b.next() // { value: 2, done: false }
b.next() // { value: 3, done: false }
b.next() // { value: 4, done: false }
b.next() // { value: 5, done: false }
b.next() // { value: undefined, done: true }

  • (4)其他场合
    由于数组的迭代会调用迭代器接口,所以任何接受数组作为参数的场合,其实都调用了迭代器接口。下面是一些例子。
  • for...of
  • Array.from()
  • Map(), Set(), WeakMap(), WeakSet()(比如new Map([['a',1],['b',2]])
  • Promise.all()
  • Promise.race()

4.5 for...of循环

只要数据结构部署了Symbol.iterator属性,即具有 iterator 接口,可以用for...of循环迭代它的成员。也就是说,for...of循环内部调用的是数据结构的Symbol.iterato方法。使用场景for...of可以使用在数组SetMap结构类数组对象Genetator对象字符串

  • 数组 for...of循环可以代替数组实例的forEach方法。
let a = ['a', 'b', 'c'];
for (let k of a){console.log(k)}; // a b ca.forEach((ele, index)=>{console.log(ele);    // a b cconsole.log(index);  // 0 1 2
})

for...in对比,for...in只能获取对象键名,不能直接获取键值,而for...of允许直接获取键值。

let a = ['a', 'b', 'c'];
for (let k of a){console.log(k)};  // a b c
for (let k in a){console.log(k)};  // 0 1 2

  • Set和Map
    可以使用数组作为变量,如for (let [k,v] of b){...}
let a = new Set(['a', 'b', 'c']);
for (let k of a){console.log(k)}; // a b clet b = new Map();
b.set('name','leo');
b.set('age', 18);
b.set('aaa','bbb');
for (let [k,v] of b){console.log(k + ":" + v)};
// name:leo
// age:18
// aaa:bbb

  • 类数组对象
// 字符串
let a = 'hello';
for (let k of a ){console.log(k)}; // h e l l o// DOM NodeList对象
let b = document.querySelectorAll('p');
for (let k of b ){k.classList.add('test');
}// arguments对象
function f(){for (let k of arguments){console.log(k);}
}
f('a','b'); // a b

  • 对象
    普通对象不能直接使用for...of会报错,要部署Iterator才能使用。
let a = {a:'aa',b:'bb',c:'cc'};
for (let k in a){console.log(k)}; // a b c
for (let k of a){console>log(k)}; // TypeError

4.6 跳出for...of

使用break来实现。

for (let k of a){if(k>100)break;console.log(k);
}

5. 生成器(详细介绍)

5.1 基本概念

Generator生成器函数是一种异步编程解决方案。 原理
执行Genenrator函数会返回一个遍历器对象,依次遍历Generator函数内部的每一个状态。Generator函数是一个普通函数,有以下两个特征:function关键字与函数名之间有个星号;
函数体内使用yield表达式,定义不同状态;

通过调用next方法,将指针移向下一个状态,直到遇到下一个yield表达式(或return语句)为止。简单理解,Generator函数分段执行,yield表达式是暂停执行的标记,而next恢复执行。

function * f (){yield 'hi';yield 'leo';return 'ending';
}
let a = f();
a.next();  // {value: 'hi', done : false}
a.next();  // {value: 'leo', done : false}
a.next();  // {value: 'ending', done : true}
a.next();  // {value: undefined, done : false}

5.2 yield表达式

yield表达式是暂停标志,遍历器对象的next方法的运行逻辑如下:
1. 遇到yield就暂停执行,将这个yield后的表达式的值,作为返回对象的value属性值。
2. 下次调用next往下执行,直到遇到下一个yield
3. 直到函数结束或者return为止,并返回return语句后面表达式的值,作为返回对象的value属性值。
4. 如果该函数没有return语句,则返回对象的valueundefined

注意:
* yield只能用在Generator函数里使用,其他地方使用会报错。

// 错误1
(function(){yiled 1;  // SyntaxError: Unexpected number
})()// 错误2  forEach参数是个普通函数
let a = [1, [[2, 3], 4], [5, 6]];
let f = function * (i){i.forEach(function(m){if(typeof m !== 'number'){yield * f (m);}else{yield m;}})
}
for (let k of f(a)){console.log(k)
}

  • yield表达式如果用于另一个表达式之中,必须放在圆括号内。
function * a (){console.log('a' + yield);     //  SyntaxErroconsole.log('a' + yield 123); //  SyntaxErroconsole.log('a' + (yield));     //  okconsole.log('a' + (yield 123)); //  ok
}

  • yield表达式用做函数参数或放在表达式右边,可以不加括号
function * a (){f(yield 'a', yield 'b');    //  oklei i = yield;              //  ok
}

5.3 next方法

yield本身没有返回值,或者是总返回undefinednext方法可带一个参数,作为上一个yield表达式的返回值。

function * f (){for (let k = 0; true; k++){let a = yield k;if(a){k = -1};}
}
let g =f();
g.next();    // {value: 0, done: false}
g.next();    // {value: 1, done: false}
g.next(true);    // {value: 0, done: false}

这一特点,可以让Generator函数开始执行之后,可以从外部向内部注入不同值,从而调整函数行为。

function * f(x){let y = 2 * (yield (x+1));let z = yield (y/3);return (x + y + z);
}
let a = f(5);
a.next();   // {value : 6 ,done : false}
a.next();   // {value : NaN ,done : false}
a.next();   // {value : NaN ,done : true}
// NaN因为yeild返回的是对象 和数字计算会NaNlet b = f(5);
b.next();     // {value : 6 ,done : false}
b.next(12);   // {value : 8 ,done : false}
b.next(13);   // {value : 42 ,done : false}
// x 5 y 24 z 13

5.4 for...of循环

for...of循环会自动遍历,不用调用next方法,需要注意的是,for...of遇到next返回值的done属性为true就会终止,return返回的不包括在for...of循环中。

function * f(){yield 1;yield 2;yield 3;yield 4;return 5;
}
for (let k of f()){console.log(k);
}
// 1 2 3 4  没有 5

5.5 Generator.prototype.throw()

throw方法用来向函数外抛出错误,并且在Generator函数体内捕获。

let f = function * (){try { yield }catch (e) { console.log('内部捕获', e) }
}let a = f();
a.next();try{a.throw('a');a.throw('b');
}catch(e){console.log('外部捕获',e);
}
// 内部捕获 a
// 外部捕获 b

5.6 Generator.prototype.return()

return方法用来返回给定的值,并结束遍历Generator函数,如果return方法没有参数,则返回值的value属性为undefined

function * f(){yield 1;yield 2;yield 3;
}
let g = f();
g.next();          // {value : 1, done : false}
g.return('leo');   // {value : 'leo', done " true}
g.next();          // {value : undefined, done : true}

5.7 next()/throw()/return()共同点

相同点就是都是用来恢复Generator函数的执行,并且使用不同语句替换yield表达式。
* next()yield表达式替换成一个值。

let f = function * (x,y){let r = yield x + y;return r;
}
let g = f(1, 2);
g.next();   // {value : 3, done : false}
g.next(1);  // {value : 1, done : true}
// 相当于把 let r = yield x + y;
// 替换成 let r = 1;

  • throw()yield表达式替换成一个throw语句。
g.throw(new Error('报错'));  // Uncaught Error:报错
// 相当于将 let r = yield x + y
// 替换成 let r = throw(new Error('报错'));

  • next()yield表达式替换成一个return语句。
g.return(2); // {value: 2, done: true}
// 相当于将 let r = yield x + y
// 替换成 let r = return 2;

5.8 yield* 表达式

用于在一个Generator中执行另一个Generator函数,如果没有使用yield*会没有效果。

function * a(){yield 1;yield 2;
}
function * b(){yield 3;yield * a();yield 4;
}
// 等同于
function * b(){yield 3;yield 1;yield 2;yield 4;
}
for(let k of b()){console.log(k)}
// 3
// 1
// 2
// 4

5.9 应用场景

  1. 控制流管理
    解决回调地狱:
// 使用前
f1(function(v1){f2(function(v2){f3(function(v3){// ... more and more})})
})// 使用Promise
Promise.resolve(f1).then(f2).then(f3).then(function(v4){// ...},function (err){// ...}).done();// 使用Generator
function * f (v1){try{let v2 = yield f1(v1);let v3 = yield f1(v2);let v4 = yield f1(v3);// ...}catch(err){// console.log(err)}
}
function g (task){let obj = task.next(task.value);// 如果Generator函数未结束,就继续调用if(!obj.done){task.value = obj.value;g(task);}
}
g( f(initValue) );

  1. 异步编程的使用 在真实的异步任务封装的情况:
let fetch = require('node-fetch');
function * f(){let url = 'http://www.baidu.com';let res = yield fetch(url);console.log(res.bio);
}
// 执行该函数
let g = f();
let result = g.next();
// 由于fetch返回的是Promise对象,所以用then
result.value.then(function(data){return data.json();
}).then(function(data){g.next(data);
})

参考资料

1.MDN 迭代器和生成器
2.ES6中的迭代器(Iterator)和生成器(Generator)

本部分内容到这结束

欢迎关注我的微信公众号【前端自习课】

批量生成数组_JavaScript【重温基础】13.迭代器和生成器相关推荐

  1. python基础:迭代器、生成器(yield)详细解读

    1. 迭代器 迭代器是访问集合元素的一种方式.迭代器对象从集合的第一个元素开始访问,知道所有的元素被访问完结束.迭代器只能往前不会后退,不过这也没什么,因为人们很少在迭代途中往后退. 1.1 使用迭代 ...

  2. Python基础4 迭代器,生成器,装饰器,Json和pickle 数据序列化

    本节内容 迭代器&生成器 装饰器 Json & pickle 数据序列化 软件目录结构规范 作业:ATM项目开发 1.列表生成式,迭代器&生成器 列表生成式 孩子,我现在有个需 ...

  3. 饮冰三年-人工智能-Python-16Python基础之迭代器、生成器、装饰器

    一:迭代器: 最大的特点:节省内存 1.1 迭代器协议 a:对象必须提供一个next方法, b:执行方法要么返回迭代中的下一项,要么抛弃一个Stopiteration异常, c:只能向后不能向前. 1 ...

  4. Python_基础_(迭代器,生成器)

    一,迭代器 1,迭代器协议:对象必须具有一个next方法,执行该方法要么返回迭代中的下一项,要么就引起一个StopInteration异常,以来终止迭代(只能往后走,不能向前进) 2,可迭代对象:实现 ...

  5. 15款最好用的腾讯短链接url批量生成工具 - 值得收藏

    整理了15款国内最好用的腾讯短链接(url.cn)批量生成工具,拿走不谢! 1.青桃短链接 平台官网:http://qturl.cn 青桃短链接是一个老牌的第三方短链接服务平台了.对于其技术支持和服务 ...

  6. Python办公自动化实战 13 | Python-docx库:Python与Word的完美结合_学员考试准考证批量生成

    一.专题内容简介 本次专题任务主要讲解了如何利用Python-docx库操作Word文档实现自动批量生成NCT考试准考证的案例. 主要用到了以下知识: 利用 python-docx 库向 word 中 ...

  7. EXCEL 批量生成sheet表+生成超链接目录+某列按多条件去重罗列+提取单元格中的字母、数字

    文章目录 前言 一.EXCEL 公式实现多个条件值匹配 二.EXCEL 工作薄下SHEET表太多时你可以这样做 1.生成多个相同表头的SHEET表 1.1.批量生成SHEET表 1.2.批量更改多个S ...

  8. 黄聪:如何使用CodeSmith批量生成代码(转:http://www.cnblogs.com/huangcong/archive/2010/06/14/1758201.html)...

    先看看CodeSmith的工作原理: 简单的说:CodeSmith首先会去数据库获取数据库的结构,如各个表的名称,表的字段,表间的关系等等,之后再根据用户自定义好的模板文件,用数据库结构中的关键字替代 ...

  9. 如何使用CodeSmith批量生成代码

    在上一篇我们已经用PowerDesigner创建好了需要的测试数据库,下面就可以开始用它完成批量代码生成的工作啦. 下面我会一步步的解释如何用CodeSmith实现预期的结果的,事先声明一下,在此只做 ...

最新文章

  1. 南京人工智能高等研究院孔慧:多向技术驱动,让企业具备长久竞争力
  2. 修改服务器的时区为gmt,将GMT转换为服务器本地时区C#
  3. 解决浏览器存储问题,不得不了解的cookie,localStorage和sessionStorage
  4. centos中nodejs npm环境完全删除
  5. DataFountain新上计算机视觉比赛-20万巨奖数钢筋
  6. 1006.arm 板中杀死进程
  7. Java 编程下的并发线程之间的同步代码块死锁
  8. Vue-cli搭建vue基础项目
  9. 带新手玩转MVC——不讲道理就是干(下)
  10. 20165320 第七周学习总结
  11. E - What Is Your Grade?
  12. php伪静态后301,php伪静态htaccess实现301重定向方法
  13. make[1]: *** [storage/perfschema/unittest/CMakeFiles/pfs_connect_attr-t.dir/all] 错误 2 解决方法...
  14. C# Graphics 透明 gif 进度条
  15. 对象和map的相互转换
  16. 欧姆龙r88d系列服务器说明书,欧姆龙R88D-KN10H-ECT-Z用户手册 - 广州凌控
  17. PHP解决IE浏览器下载文件名乱码问题
  18. 【波形发生器(附源码)】基于DAC+DMA+UART的幅值、频率可调的正弦波、方波、三角波发生器
  19. 微软收购雅虎要中国政府审批?
  20. tenacity 报错_Python Tenacity 实现重试机制

热门文章

  1. 时光机穿梭---撤销修改
  2. Tensorflow的中文网站
  3. Angular4.0.0正式发布,附新特性及升级指南
  4. 在macOS搭建React Native for IOS开发环境
  5. centos安装zookeeper
  6. 为什么MySQL索引要使用 B+树,而不是其它树形结构?
  7. leetcode 448. 找到所有数组中消失的数字(Java版)
  8. 【Docker】安装与常用命令
  9. 【Mybatis框架】从零开始学Mybatis框架——使用示例
  10. 【Java TreeMap】测试TreeMap的使用、Comparabe自定义类的自定义排序方式