迭代模式:一种设计模式,无需知道如何迭代就完成了迭代操作。
ES6正式支持迭代模式,引入迭代器和生成器
对象实现Iterable接口,会有一个Symbol.iterator属性,属性引用默认迭代器。默认迭代器像一个迭代工厂,也就是一个函数,调用之后会产生一个实现Iterator接口的对象(按需创建的一次性对象)。
可迭代对象实现Iterable接口的对象就可以被实现迭代器Iterator接口的对象消费,即迭代。包含元素有限且具有无歧义的遍历顺序。
实现Iterable接口的要求
- 支持迭代的自我识别能力
- 创建实现Iterator接口的对象的能力
如果对象原型链上的父类实现了Iterable接口,这个对象也就实现了这个接口
本质:实现一个Symbol.iterator方法,该方法返回一个可迭代对象(包含一个返回迭代结果对象的next方法)
实现了Iterable接口的内置类型有:字符串、数组、映射、集合、arguments对象、NodeList等DOM集合类型
检查是否存在默认迭代器属性可以暴露
接受可迭代对象的原生语言特性包括
- for-of循环原生消费者:会忽略done==true的value值
- 数组解构:将数组元素赋值给多个变量
- 扩展操作符…,将数组的方括号去掉,变成参数序列
- Array.from():转换成数组
- 创建集合 new set(对象);
- 创建映射 对象
- Promise,all()接收由期约组成的可迭代对象
- Promise.race()
- yield*操作符:在生成器中使用
迭代器
迭代器必须通过连续调用next()方法才能连续取得值,方法返回一个IteratorResult对象,该对象属性:value、done(布尔值,是否耗尽)
每个迭代器有一个游标对可迭代对象的一次性有序遍历。不同的迭代器之间没有联系。迭代器维护者一个指向迭代对象的引用(不绑定可迭代对象的某一时刻,反应实时变化),因此迭代器会阻止垃圾回收程序回收可迭代对象。
//实现了Iterable接口
//调用默认的迭代工厂函数返回
//显式的迭代器实现
class Foo{
[Symbol.iterator](){
return{
next(){
return{done:false,value:'foo'};
}
}
}
}
let f=new Foo();
let arr = [1,2,3];
let map = arr[Symbol.iterator]();//生成迭代对象
arr.splice(1,0,'bar');//在1处新插入值
console.log(map.next());//{value: 1, done: false}
console.log(map.next());//{value:‘bar', done: false}
console.log(map.next());//{value: 2, done: false}
console.log(map.next());//{value: 3, done: false}
console.log(map.next());//{value: undefined, done: true}
提前关闭迭代器
实质上就是查找并执行Symbol.iterator中的return()方法
迭代器实例.return();//关闭迭代器,返回一个迭代器结果对象
throw
catch
class Counter {
constructor(limit) {this.limit = limit;}
[Symbol.iterator]() {
let count = 1;
let limit = this.limit;
return {//next、return两个方法
next() {
if (count <= limit) {return { done: false, value: count++ };
} else {return { done: true, value: undefined };}
},
return() {//外界提前终止
console.log('Exiting early');
//释放清理内存
return { done: true };
}
};//return
}//Symbol
}
let counter1 = new Counter(5);
for (let i of counter1) {
if (i > 2) {break; }//3执行return后,跳出迭代循环
console.log(i);
}// 1 2 Exiting early
let counter2 = new Counter(5);
try {
for (let i of counter2 ){
if (i > 2) {throw 'err'; }
console.log(i);
}
} catch(e) {console.log(e)}// 1 2 Exiting early err
let counter3 = new Counter(5);
let [a, b] = counter3;//解耦操作没有消费所有值
// Exiting early
console.log(a);//1
console.log(b);//2
解耦没有消费所有,但是赋值仍然有效。看a、b值
生成器
生成器:特殊函数
箭头函数不能用来定义生成器函数
//声明
function* 生成器函数名(){}
//表达式
let f=function* (){};
//字面量
let f={
* 生成器函数名(){}
}
//类实例的生成器函数
class peo {
* 生成器函数名(){}
}
//类静态方法的生成器函数
class peo {
static * 生成器函数名(){}
}
生成器拥有在一个函数块内暂停和恢复代码执行的能力。可以自定义迭代器和实现协程
生成器对象一开始处于suspended状态,第一次next()开始执行函数
生成器对象实现Iterable接口,因此可用在任何消费可迭代对象的地方。
对象具有next(),该方法返回一个IteratorResult对象,
该对象属性:value函数返回值 yield return
done(布尔值,是否执行结束) yield返回false
生成器特殊在支持yield关键字,yield可以暂停执行生成器函数,yield必须直接位于生成器定义中,就算是出现在生成器函数内部嵌套的非生成器函数中也会报错。
生成器函数可以有多个对象,一个生成器对象的next()不会影响另一个
yield实现输入输出,函数的中间参数
生成器函数的传参只在第一次调用生成器,第一次调用生成器并没有开始执行代码
yield关键字,指向传给next方法的第一个参数
如果遇到yield表达式,就暂停后面的执行,并将紧跟yield后面的表达式的值作为返回对象的value。
被暂停的执行只有在下一次调用next方法时候才会被执行。
function* generatorFn(once) {
console.log(once);
console.log(yield);//先yield就出去了,下一次再console.log
console.log(once);
console.log(yield);//yield的返回值
console.log(once);
return "end";
}
let generatorObject1 = generatorFn("once");//并没有执行,只是传参
console.log("---");
console.log(generatorObject1.next("1"));
console.log("---");
console.log(generatorObject1.next("2"));
console.log("---");
console.log(generatorObject1.next("3"));
猜一猜输出?
个人理解:注意对比console的次数
第一次调用next方法时候只执行了一次console,他的首参应该是不能通过yield来操作的,因为yield意味着中断生成器,其实每一次的console.log都是排在上一次yield后面的操作,优先级比这一次的yield高。第一次的yield没有办法获得前一个yield的操作。
yield要等的计算是他右边的式子,作为返回值。
yield产生可迭代对象
yield关键字可以将跟在他之后的可迭代对象序列化为一连串值。
yield 1;
yield 2;
yield 3;
//等同于
for(const x of [1,2,3]){
yield x;
}
//等同于
yield *[1,2,3];
yield实现递归
function* nTimes(n){
if(n>0){//1
yield* nTimes(n-1);//空执行
yield n-1;//0
}
}
for (const x of nTimes(3)){
console.log(x);
}
//0,1,2
yield 实现深度优先遍历
生成器作为默认迭代器
一个类的默认迭代器可以用一行代码产出类的内容
class F{
construction(){
this.values=[1,2,3];
}
* [Symbol.iterator](){//生成器作为迭代器
yield* this.values;
}
}
//
const f=new F();
for(const x of f){
console.log(x);
}
//1
//2
//3
提前关闭生成器
return();//无法恢复
throw();//将一个错误注入生成器对象中,如果生成器函数内部错误未被处理,关闭;如果生成器内部有catch模块对错误进行处理,那么生成器就不会关闭,而且还可以恢复执行。但是错误处理会跳过对应的yield值。
function *returnFunc () {
try {
yield 1;
yield 2;
} finally {
yield 3;
yield 5;
}
}
const it = returnFunc();
console.log(it.next());//1
console.log(it.return(4));//执行finally3
console.log(it.next());//5
console.log(it.next());//4 done=true
function* generatorFn() {
for (const x of [1, 2, 3]) {
yield x;
}
return "4G";
}
const g = generatorFn();
console.log(g.next()); // { done: false, value: 1 }
console.log(g.return(4)); // { done: true, value: 4 }
console.log(g.next()); // { done: true, value: undefined }
const t = generatorFn();
console.log(t); // generatorFn {<suspended>}
try { t.throw('foo');
} catch (e) { console.log(e); } // foo
console.log(t); // generatorFn {<closed>}
function *throwFunc () {
try {
yield 1;
yield 2;
} catch (e) {
console.error(e);
yield 3;
yield 5;
}
}
const it = throwFunc();
console.log(it.next());//1
console.log(it.throw(new Error('throw error')));//throw error错误处理跳过了2这个值
console.log(it.next());//3
console.log(it.next());//5
高级生成器特性
除了创建迭代器,基于暂停计算的能力,生成器还有其他高级特性
迭代器的应用
//返回一个可迭代对象,迭代结果是对传入的每个值遍历应用f的结果
function map(iterable,f){
let iterator=iterable[Symbol.iterator]();
return {
[Symbol.iterator](){return this;},
next(){
let v=iterator.next();
if(v.done){
return v;
}else{
return {value: f(v.value)};
}
}
};
}