ES6迭代器(Iterator)和生成器(Generator)

平时我们迭代数据用得最多的应该就是for循环了

来看个简单的例子

     var colors = ["red", "green", "blue"];
      for (var i = 0, len = colors.length; i < len; i++) {
          console.log(colors[i]);
      }

如上循环是很简单,但是一旦使用多个循环嵌套时,就需要为每一个循环定义变量,来记录每一次执行迭代时所处集合中的位置,一不小心就会误用了其他for循环的变量,导致程序错误。然而迭代器可以简化操作,降低程序异常。接下来我们就一起看看什么事迭代器

一、什么是迭代器

迭代器:是一种特殊对象,他具有一些专门为迭代过程设计的专有接口,所有的迭代器对象都有一个next()方法,每次调用都返回一个结果对象。结果对象有两个属性:一个是value,表示下一个将要返回的值;另一个是done,他是一个布尔类型的值,在当没有可返回的数据时返回true,预示着迭代结束

现在使用ECMAScript 5 语法构建一个迭代器:

 function createIterator() {
       var i = 0;
       return {
           next: function () {
               var done = (i >= items.length);
               var value = !done ? items[i++] : undefined;
               return {
                   done: done,
                   value: value
               }
           }
       }
   }

 var iterator = createInterator([1, 2, 3]);
 console.log(iterator.next()); // "{value:1,done:false}"
 console.log(iterator.next());// "{value:2,done:false}"
 console.log(iterator.next());// "{value:3,done:false}"
 console.log(iterator.next());// "{value:undefined,done:true}"
 // 当调用次数超过实际值后,不管再调用多少次结果都会是如下值
 console.log(iterator.next());// "{value:undefined,done:true}"
 

在这个案例中createIterator()方法会返回一个对象,该对象拥有一个next()方法,每次调用next()方法,都会判断 i 是否大于等于 当前实参的长度;大于或等于时done 即为true,反之亦然。然后将done取反;当done为truevalue 赋值为undefined;否则将i+1,再取item 对应下标的值赋值于value。其实ES6迭代器实现原理和案例也就类似。

二、什么是生成器

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

	// 在createIterator()前的 * 表示他是一个生成器
    function *createIterator() {
       yield 1;
       yield 2;
       yield 3;
    }
  //生成器的调用和 普通函数相同,只是他返回的是一个迭代器
  var iterator = createInterator();
  console.log(iterator.next().value); // 1
  console.log(iterator.next().value); // 2
  console.log(iterator.next().value); // 3    
  

需要注意的是,每当执行完一条yield 乬后函数就会自动停止执行。 如上面的案例,当函数初始化执行完yield 1 之后,函数便不会向下执行,直到再次调用迭代器的next()方法才会继续向下执行yield 2语句

三、生成器函数表达式

语法
funciont *(){}

function关键字和小括号中间添加一个星号

看个案例

let createator = function *(items){
	for(let i = 0; i<items.length; i++){
		yield iems[i];
	}
}

let iterator = createIterator([1,2,3]);

console.log(iterator.next()); // "{value:1,done:false}"
console.log(iterator.next()); // "{value:2,done:false}"
console.log(iterator.next()); // "{value:3,done:false}"
console.log(iterator.next()); // "{value:undefined,done:true}"

// 之后所有的调用都会返回相同的内容
console.log(iterator.next()); // "{value:undefined,done:true}"

在该案例中createator ()已经不再是一个普通的函数申明,而是一个生成器函数表达式;
注意:不能使用箭头函数来创建生成器

四、 生成器对象的方法

1、 使用ECMAScript 5 对象字面量风格来定义一个生成器对象


let obj = {
	createIterator:function *(items){
		for(let i=0; i<items.length; i++){
			yield items[i];
		}
	}
}

let iterator = obj.createIterator([1,2,3]);

其实上面案例可以使用ECMAScript 6 申明函数方法的方式来改写

let obj = {
	*createIterator(items){
		for(let i=0; i<items.length; i++){
			yield items[i];
		}
	}
}

let iterator = obj.createIterator([1,2,3]);

咋一看这个是不是跟普通的函数申明很像呢;其实* 是可以与方法名留个空格的如* createIterator(items);但是我还是感觉在一起好一点(不给他人留任何机会,哈哈)

五、 可迭代对象和 for-of

可迭代对象具有Symbol.iterator属性,是一种与迭代器密切相关的对象;Symbol.iterator通过指定的函数可以返回一个作用于附属对象的迭代器。在es6中所有集合对象(数组、set和map集合)和字符串都是可迭代对象,这些对象都有默认的迭代器

来访问一下默认的迭代器

let values = [1,2,3];
let iterator = values[Symbol.iterator];

console.log(iterator.next()); // "{value:1,done:false}"
console.log(iterator.next()); // "{value:2,done:false}"
console.log(iterator.next()); // "{value:3,done:false}"
console.log(iterator.next()); // "{value:undefined,done:true}"

案例中通过Symbol.iterator获取数组的values的默认迭代器,并用它遍历数组中的元素。在JavaScript引擎中执行for-of循环语句时也会有类似的处理过程

接下来看个ECMAScript的新特性 for-of

let values = [1,2,3];
for(let num of values){
	console.log(num);
}

上面代码会输出
1
2
3

这个 for-of 循环每执行一次都会调用可迭代对象的next() 方法,并将迭代器返回的结果对象的value属性存储在一个变量中,选好将持续执行这一过程知道昂返回对象的done属性的值为true 为止(可以结合‘什么是迭代器’的那一节的案例来理解看你更容易些)

使用Symbol.iterator 来检查对象是否为可迭代对象

function isIterable(object){
	return typeof object[Symbol.iterator] === "function";
}

console.log(isIterable([1,2,3])); // true
console.log(isIterable("hello")); // true
console.log(isIterable(new Map())); // true
console.log(isIterable(new Set())); // true
console.log(isIterable(new weakMap())); // false
console.log(isIterable(new weakSet())); // false

其实上一个案例的for-of循环执行之前也会做类似的对象检查(只是这个是JavaScript引擎做的我们不知道而已)

六、向迭代器传递参数

经过如上案例,已知迭代器可以通过next()向外传值, 也可以通过yield 关键字来生成值,

如果给迭代器的next() 方法传递参数, 则这个参数就会替代生成器内部上一条yield 语句的

返回值。 像要实现像异步编程这样的高级功能,这个迭代器传值的能力会起到很大的作用

function *createIterator(){
	let first = yield 1;
	let second = yield first + 2;
	yield second + 3;
}

let iterator = createIterator();

console.log(iterator.next()); // "{value:1,done:false}"
console.log(iterator.next(4)); // "{value:6,done:false}"
console.log(iterator.next(5)); // "{value:8,done:false}"
console.log(iterator.next()); // "{value:undefined,done:true}"

注意:第一次调用next() 方法无论传入什么参数都会被丢弃,这个是迭代器的一个特性

由于给next() 方法的参数会代替上一次yield 的返回值,而在第一次调用next() 方法前不会

执行执行任何yield语句,因此在第一次调用next() 时传递参数毫无意义

而 next(4) 是将4 作为参数 赋值给了变量 first

这里有个不同就是 在一个含有 yield 的语句中, 表达式右侧等价于 第一次调用next() 方法

后的下一个返回值, 表达式左侧等价于第二次调用next() 方法后,在函数继续执行前得到

的返回值。

iterator.next(4) 方法传入参数为 4, 他会被赋值给变量 first, 函数继续执行…

其中 let first = yield 1;语句 first 值已经是4 了 , 然后语句let second = yield first + 2; 执行了表达式右侧 yield first + 2 计算 出该次调用后的返回值 , 依次类推…

有点绕,修改执行一下代码,看看程序具体执行流程 来加深理解

> function *createIterator(){
...     let first = yield 1;
...     console.log('first==>',first)
...     let second = yield first + 2;
...     console.log('second==>',second)
...     yield second + 3;
...     console.log('final second and first ==>',first,second)
... }
undefined
>
> let iterator = createIterator();
undefined
> console.log(iterator.next());
{ value: 1, done: false }
undefined
> console.log(iterator.next(4));
first==> 4
{ value: 6, done: false }
undefined
> console.log(iterator.next(5));
second==> 5
{ value: 8, done: false }
undefined
> console.log(iterator.next());
final second and first ==> 4 5
{ value: undefined, done: true }
undefined
> console.log(iterator.next());
{ value: undefined, done: true }
undefined
>               

七、使用任务迭代器实现异步取值

有了上一步的传值经历,这里我们第一个任务函数来完成 值的向下传递;

看如下dome 将result.value 传入 下一次 next() 方法 直到任务完成

function run(taskDef){
	// 创建一个无使用限制的迭代器
	let task = taskDef();
	
	// 开始执行任务
	let result = task.next();
	
	// 循环调用next()的函数
	function step(){
		
		// 如果任务未完成,则继续执行
		if(!result.done){
			result = task.next(result.value);
			step();
		}
	}
	
	// 开始迭代执行
	step();
}

调用上面的方法并传入一个迭代器

run(function*(){
	let value = yield 6;
	console.log(value); // 6
	
	value = yield value + 3;
	console.log(value); // 9
})

看方法调用后输入了 64, 其中 6 取自 yield 6 语句 回传给value 的值,而最后的value 则是取的上一个yield 返回的值加上3

下面就进一步改进上面的方法,完成一个使用迭代器对象完成文件读取的操作

function run(taskDef){
	// 创建一个无使用限制的迭代器
	let task = taskDef();
	
	// 开始执行任务
	let result = task.next();
	
	// 循环调用next()的函数
	function step(){
		
		// 如果任务未完成,则继续执行
		if(!result.done){
			if(typeof result.value === function){
				result.value(function(err,data){
					if(err){
						result = task.throw(err);
						return;
					}
					
					result = task.next(data);
					step();
				})
			}else{
				result = task.next(result.value);
				step();
			}

		}
	}
	
	// 开始迭代执行
	step();
}

当result.value是一个函数时,任务执行器会先执行这个函数再将结果传入next()方法

如果不是一个函数直接将内容传入next()进行最后的yield返回

定义文件读取 函数

let fs = require('fs');

function readFile(filename){
	return function(callback){
		fs.readFile(filename,callback);
	}
}


// 开始调用执行
run(function *(){
	let contents = yield readFile('config.json');
	// do something...
	doSomethingMethods(contents);
	console.log('readFile done.')
})

好了这个方法就如同同步的执行了一个异步方法获取到了文件内容

  • 今天先做个简单的了解,明天继续…
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

奔跑的痕迹

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值