javaScript的迭代模式:迭代器与生成器

迭代模式:一种设计模式,无需知道如何迭代就完成了迭代操作。
ES6正式支持迭代模式,引入迭代器和生成器

对象实现Iterable接口,会有一个Symbol.iterator属性,属性引用默认迭代器。默认迭代器像一个迭代工厂,也就是一个函数,调用之后会产生一个实现Iterator接口的对象(按需创建的一次性对象)。
可迭代对象实现Iterable接口的对象就可以被实现迭代器Iterator接口的对象消费,即迭代。包含元素有限且具有无歧义的遍历顺序。

实现Iterable接口的要求

  1. 支持迭代的自我识别能力
  2. 创建实现Iterator接口的对象的能力

如果对象原型链上的父类实现了Iterable接口,这个对象也就实现了这个接口

本质:实现一个Symbol.iterator方法,该方法返回一个可迭代对象(包含一个返回迭代结果对象的next方法)

实现了Iterable接口的内置类型有:字符串、数组、映射、集合、arguments对象、NodeList等DOM集合类型
检查是否存在默认迭代器属性可以暴露
在这里插入图片描述

接受可迭代对象原生语言特性包括

  1. for-of循环原生消费者:会忽略done==true的value值
  2. 数组解构:将数组元素赋值给多个变量
  3. 扩展操作符…,将数组的方括号去掉,变成参数序列
  4. Array.from():转换成数组
  5. 创建集合 new set(对象);
  6. 创建映射 对象
  7. Promise,all()接收由期约组成的可迭代对象
  8. Promise.race()
  9. 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)};
            }
        }
    };
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值