本文是 重温基础 系列文章的第十三篇。今日感受:每次自我年终总结,都会有各种情绪和收获。系列目录:【复习资料】ES6/ES7/ES8/ES9资料整理(个人整理)【重温基础】1.语法和数据类型【重温基础】2.流程控制和错误处理【重温基础】3.循环和迭代【重温基础】4.函数【重温基础】5.表达式和运算符【重温基础】6.数字【重温基础】7.时间对象【重温基础】8.字符串【重温基础】9.正则表达式【重温基础】10.数组【重温基础】11.Map和Set对象【重温基础】12.使用对象本章节复习的是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迭代过程创建一个指针对象,指向当前数据结构的起始位置。也就是说,迭代器对象本质上,就是一个指针对象。第一次调用指针对象的next方法,可以将指针指向数据结构的第一个成员。第二次调用指针对象的next方法,指针就指向数据结构的第二个成员。不断调用指针对象的next方法,直到它指向数据结构的结束位置。每一次调用next方法,都会返回数据结构的当前成员的信息。具体来说,就是返回一个包含value和done两个属性的对象。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接口的数据结构有:ArrayMapSetStringTypedArray函数的 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’扩展运算符扩展运算符(…)也会调用默认的 Iterator 接口。let a = ‘hello’;[…a]; // [‘h’,’e’,’l’,’l’,‘o’]let a = [‘b’, ‘c’];[‘a’, …a, ’d’]; // ‘a’, ‘b’, ‘c’, ’d’yieldyield后面跟的是一个可迭代的结构,它会调用该结构的迭代器接口。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…ofArray.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可以使用在数组,Set和Map结构,类数组对象,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 c console.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 cfor (let k in a){console.log(k)}; // 0 1 2Set和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 cfor (let k of a){console>log(k)}; // TypeError4.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方法的运行逻辑如下:遇到yield就暂停执行,将这个yield后的表达式的值,作为返回对象的value属性值。下次调用next往下执行,直到遇到下一个yield。直到函数结束或者return为止,并返回return语句后面表达式的值,作为返回对象的value属性值。如果该函数没有return语句,则返回对象的value为undefined 。注意: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); // SyntaxErro console.log(‘a’ + yield 123); // SyntaxErro console.log(‘a’ + (yield)); // ok console.log(‘a’ + (yield 123)); // ok}yield表达式用做函数参数或放在表达式右边,可以不加括号。function * a (){ f(yield ‘a’, yield ‘b’); // ok lei i = yield; // ok}5.3 next方法yield本身没有返回值,或者是总返回undefined,next方法可带一个参数,作为上一个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 135.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// 外部捕获 b5.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// 45.9 应用场景控制流管理解决回调地狱:// 使用前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();// 使用Generatorfunction * 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) );异步编程的使用在真实的异步任务封装的情况: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对象,所以用thenresult.value.then(function(data){ return data.json();}).then(function(data){ g.next(data);})参考资料1.MDN 迭代器和生成器 2.ES6中的迭代器(Iterator)和生成器(Generator)本部分内容到这结束Author王平安E-mailpingan8787@qq.com博 客www.pingan8787.com微 信pingan8787每日文章推荐https://github.com/pingan8787…ES小册es.pingan8787.com