本文是 重温基础 系列文章的第十三篇。
今日感觉:每次自我年终总结,都会有各类情绪和收获。html
系列目录:前端
本章节复习的是JS中的迭代器和生成器,经常用来处理集合。node
前置知识:
JavaScrip已经提供多个迭代集合的方法,从简单的for
循环到map()
和filter()
。
迭代器和生成器将迭代的概念直接带入核心语言,并提供一种机制来自定义for...of
循环的行为。git
本文会将知识点分为两大部分,简单介绍和详细介绍:
简单介绍,适合基础入门会使用的目标;
详细介绍,会更加深刻的作介绍,适合理解原理;github
当咱们使用循环语句迭代数据时,需初始化一个变量来记录每一次迭代在数据集合中的位置:正则表达式
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)。json
迭代器是一种特殊对象,它具备一些专门为迭代过程设计的专有接口,全部的迭代器对象都有一个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新增的生成器,可使得建立迭代器对象的过程更加简单。
生成器是一种返回迭代器的函数,经过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}"
复制代码
生成器的适用返回很广,能够将它用于全部支持函数使用的地方。
Iterator是一种接口,为各类不一样的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就能够完成迭代操做(即依次处理该数据结构的全部成员)。
Iterator三个做用:
for...of
消费;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 }
复制代码
若数据可迭代,即一种数据部署了Iterator接口。
ES6中默认的Iterator接口部署在数据结构的Symbol.iterator
属性,即若是一个数据结构具备Symbol.iterator
属性,就能够认为是可迭代。
Symbol.iterator
属性自己是函数,是当前数据结构默认的迭代器生成函数。执行这个函数,就会返回一个迭代器。至于属性名Symbol.iterator
,它是一个表达式,返回Symbol
对象的iterator
属性,这是一个预约义好的、类型为 Symbol 的特殊值,因此要放在方括号内(参见《Symbol》一章)。
原生具备Iterator接口的数据结构有:
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']
复制代码
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()
只要数据结构部署了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 c
a.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 c
for (let k in a){console.log(k)}; // 0 1 2
复制代码
for (let [k,v] of b){...}
。let a = new Set(['a', 'b', 'c']);
for (let k of a){console.log(k)}; // a b c
let 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
复制代码
使用break
来实现。
for (let k of a){
if(k>100)
break;
console.log(k);
}
复制代码
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}
复制代码
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
}
复制代码
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返回的是对象 和数字计算会NaN
let 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
复制代码
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
复制代码
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
复制代码
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}
复制代码
相同点就是都是用来恢复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;
复制代码
用于在一个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
复制代码
// 使用前
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) );
复制代码
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)
本部份内容到这结束
Author | 王平安 |
---|---|
pingan8787@qq.com | |
博 客 | www.pingan8787.com |
微 信 | pingan8787 |
每日文章推荐 | github.com/pingan8787/… |
ES小册 | js.pingan8787.com |
欢迎关注个人微信公众号【前端自习课】