整理学习——ES6之迭代器和生成器

概述

JavaScript目前有四种数据集合,分别为数组Array、对象Object、Set对象和Map对象。如果说这四种数据集合有着各自的获取接口,那么必定会造成开发成本和维护成本的增加。于是提出了一个新的接口机制,Iterator,来为这几种不同的数据集合,甚至于不同的数据结构提供统一的访问机制。任何数据结构只要部署了Iterator接口,就可以完成遍历操作。
PS1:是大写的i不是小写的L,不过我估计应该没人弄混过吧。

迭代器

在JavaScript中,迭代器是一个定义了序列并可能返回一个值直到它迭代结束的对象。更具体的说,它是通过next()方法实现迭代器协议的任意对象。next()是一个返回两个属性的对象,这两个属性是:

  1. value,序列中的下一个值
  2. done,这个值如果已经迭代到序列中的最后一个值,则为true
    如果以上两个属性一起存在,那么它是迭代器的返回值。
    一个迭代器对象一旦创建,那么这个迭代器对象便可以重复使用next(),进行显示迭代。迭代一个迭代器过程通常被称作是消耗迭代器,因为它通常只会做一次。当最后一个值被调用之后,应该再调用next()并返回{done: true}
    在JavaScript中大多数迭代器都是简单的返回在按顺序相关联的数组元素的数组迭代器。如果不深入思考的话,就可能会一个错误理解——所有的迭代器都能被解释为数组。但是实际上,数组需要按照他们整体所需进行内存的分配,而迭代器是不需要的。迭代器仅在必要的时候使用来表达无限大小的序列,例如在0到无穷大之间的整数。
    示例:
function makeRangeIterator(start = 0, end = Infinity, step = 1){
	let nextIndex = start;
	let iterationCount = 0;
	const rangeIterator = {
		next: function(){
			let result;
			if(nextIndex < end){
				result =  {
					value: nextIndex, 
					done: false
				};
				nextIndex += step;
				iterationCount ++;
				return result;
			}
			return  {value: iterationCount, done: true};
		}
	}
	return rangeIterator;
}

let it=  makeRangeIterator(1, 10, 2);
let result = it.next();
while(!result.done){
	console.log(result.value);
	result = it.next();
}

生成器

虽然迭代器是一个十分有用的工具,但是由于需要显式地去维护其内部状态(通过参数控制其迭代方式),所以需要非常谨慎地进行创建。生成器函数提供了一个非常强大的选择:他允许你定义一个执行不连续的简单函数的迭代器。生成器Generator使用function*创建。初始化的时候,生成器函数会返回一个被称作为Generator的迭代器来替代执行代码。当一个值调用生成器的next()方法时,生成器函数将会执行直到遇到yield关键字。

示例:

function* makeRangeIterator(start = 0, end = 100, step = 1){
    for(let i = start;i < end;i += step)
        yield i;
}

也可以使用构造函数GeneratorFunctionfunction* expression定义生成器函数

function* name([param[,param[, …param]]]){startements}

name:函数名
param:要传递给函数的参数的名称
statements:JS语句
生成器函数在执行时能暂停,后续又能从暂停出继续执行。
调用一个生成器函数并不会马上执行其中的语句,而是返回这个生成器的迭代器对象。当这个迭代器的next()方法被首次或者继续调用时,内部语句会执行到第一个出现yield的位置位置,yield后紧跟迭代器要返回的值。或者,如果使用的是yield*,则表示将执行权移交给另一个生成器函数,而当前生成器暂停执行。
其内部的next()方法返回一个对象,这个对象包含两个属性:

  1. value:表示本次yield表达式的返回值
  2. done:Boolean,表示生成器后续是否还有yield语句,即生成器函数是否已经执行完毕并返回。
    调用next方法是,如果传入参数,那么这参数会传给上一条执行的yield语句左边的变量
    示例
function* sendProp(){
	yield 10;
	x = yield 'foo';
	yield x;
}
var obj = sendProp();
console.log(obj.next());
//Output: {value: 10, done: false}
console.log(obj.next());
//Output: {value: "foo", done: false}
console.log(obj.next(100));
//Output: {value: 100, done: false}
console.log(obj.next());
//Output: {value: undefined, done: true}

不过,当在生成器函数中显式return时,会导致生成器立即变为完成状态,即调用next()方法返回的对象的donetrue。若return后面跟了一个值,那么这个值会作为当前调用的next()方法返回的value值。

yield和yield*

yield
[rv] = yield [expression];

rv:返回传递给生成器的next()方法的可选值,以恢复其执行
expression:定义通过迭代器协议从生成器函数返回的值。如果省略则返回undefined。

yield关键字是生成器函数执行暂停,yield后面的表达式的值返回给生成器的调用者。实际上,yield关键字返回的是一个IteratorResult对象,一样也是value和done。此处的value表示的是对yield表达式求值的结果,而done是表示生成器函数是否完全完成。
生成器函数在遇到yield表达式的时候,生成器的代码奖杯暂停运行,直到生成器的next()方法被调用。每次调用生成器的next()方法是,生成器都会恢复执行,直到遇到以下的某个值

  1. yield:导致生成器再次暂停并返回生成器的新值。下一次调用next()时,在yield之后紧接的语句继续执行。
  2. throw:用于从生成器中抛出异常。这个让生成器完全停止执行,并在调用者中继续执行,正如通常情况下抛出异常一样。
  3. 到达生成器函数的结尾。在这种情况下,生成器的执行结束,并且IteratorResult给调用者返回undefined并且down为true
  4. 到达return语句。在这种情况下,生成器的执行结束,并将IteratorResult返回给调用者,其值value是由return语句指定的,并且done为true。

如果将参数传递给生成器的next()方法,则该值将成为生成器当前yield操作返回的值。

yield*

该表达式用于委托给另一个生成器Generator或可迭代对象。也就是在生成器的输出序列中插入另一个生成器的输出序列。

yield* [[expression]];

expression:返回一个可迭代对象的表达式。

  1. yield*表达式会将操作数的进行迭代然后返回其中的每一个值。
  2. yield*表达式本身的值是迭代器在关闭时的返回的值,也就是done是true时返回的值。

生成器示例

接收参数

function* idMaker(){
	var index = arguments[0] || 0;
	while(true){
		yield index++;
		if(index > 100){
			return;
		}
	}
}
var gen = idMaker(5);
console.log(gen.next().value);
console.log(gen.next().value);

yield*委托给其他生成器

function* g1(){
	yield 2;
	yield 3;
	yield 4;
}
function* g2(){
	yield 1;
	yield* g1();
	yield 5;
}
var iterator = g2();
console.log(iterator.next());
//Output: {value: 1, done: false}
console.log(iterator.next());
//Output: {value: 2, done: false}
console.log(iterator.next());
//Output: {value: 3, done: false}
console.log(iterator.next());
//Output: {value: 4, done: false}
console.log(iterator.next());
//Output: {value: 5, done: false}
console.log(iterator.next());
//Output: {value: undefined, done: true}

yield*委托给其他可迭代对象

function* g3(){
	yield* [1,2];
	yield* "34";
	yield* arguments;
}
var iterator = g3(5,6);

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

由上述代码可以看出,yield*还可以yield其他任意可迭代对象(数组、字符串、arguments对象)。而且对于字符串来说,会把字符作为可迭代对象中的一个元素,单独返回字符。

传递参数

function* createIterator(){
	let first = yield 1;
	console.log(first);
	let second = yield first + 2;
	console.log(first, second);
	yield second + 3;
	console.log(first, second);
}
let iterator = createIterator();
console.log(iterator.next());

//Output: {value: 1, done: false}
console.log(iterator.next(4));
/*Output: 
4
{value: 6, done: false}
此处不能输出second,因为second还未创建。
*/

console.log(iterator.next());
/*Output: 
4 undefined
{value: NaN, done: false}
这里应该传入参数给second赋值,但是在没有传入赋值
的情况下second为undefined,于是计算结果为NaN
*/
console.log(iterator.next());
//Output: {value: undefined, done: true}

由上例可知,生成器在执行时yield是返回值后直接暂停运行,直到下一次next被调用后才进行赋值。

显式返回

function* yieldAndReturn(){
	let end = "end";
	yield "Y";
	return end;
	yield "No";
}
var iterator = yieldAndReturn();
console.log(iterator.next());
//Output: {value: "Y", done: false}
console.log(iterator.next());
//Output: {value: "end", done: true}
console.log(iterator.next());
//Output: {value: undefined, done: true}

遍历二维数组并转换成一维数组

//递归遍历数组
function* iterArr(arr){
	if(Array.isArray(arr)){
		for(let i = 0;i < arr.length;i ++){
			yield* iterArr(arr[i]);
		}
	}else{
		yield arr;
	}
}

//使用for...of遍历
var arr = ['a', ['b','c'], ['d', 'e']];
for(let x of iterArr(arr)){
	console.log(x)
}
/*Output:
a
b
c
d
e
*/

//用迭代器展开
var arr = ['a', ['b', ['c',['d','e']]]];
var gen = iterArr(arr);
arr = [...gen];
console.log(arr);
//Output: ["a", "b", "c", "d", "e"]

抛出异常

function* gen(){
	while(true){
		try{
			yield 42;
		}catch(e){
			console.log("Error  caught!");
		}
	}
}
var g = gen();

g.next();
//result: {value: 42, done: false}

g.throw(new Error("Something went wrong."));
/*result:
Error  caught!
{value: 42, done: false}
*/

value和done在不同情况下的区别

阶段名称valuedone
yieldIteratorResult对象对yield表达式求值的结果表示生成器函数尚未完成
生成器函数(function*)-表示本次yield表达式的返回值表示生成器后续是否还有yield语句。即生成器是否执行完毕
迭代器-序列中的值表示序列中的值是否为最后一个值

判断可迭代对象

如果一个对象拥有迭代行为,比如它的值可以让for...of进行遍历。一些内置的数据类型,比如Array、Map,都有默认的迭代行为。而其他的类型,例如Object,则没有。
为了让对象变为可迭代对象,那么需要给这个对象添加一个@@iterator方法,意味着这个对象或者其原型链中的对象必须具有一个Symbol.iterator键的属性。
一个迭代器可能只迭代一次或者多余一次。这由程序员决定是迭代多少次。只能迭代一次的迭代器,通常从@@iterator方法中返回他们自身。而那些迭代多次的迭代器则从每次迭代的@@iterator返回一个新的迭代器。

自定义的可迭代对象

var myIterable = {
	*[Symbol.iterator](){
		yield 1;
		yield 2;
		yield 3
	}
}
for (let value of myIterable)
	console.log(value);

内置可迭代对象

JavaScript中内置的可迭代对象有String、Array、TypedArray、Map和Set,因为它们的原型对象都拥有一个Symbol.iterator方法。

用于可迭代对象的语法

一些语句和表达式专用于可迭代对象,例如for…of循环、展开语法、yield*和解构赋值。

展开语法:可以在函数调用/数组构造时, 将数组表达式或者string在语法层面展开;还可以在构造字面量对象时, 将对象表达式按key-value的方式展开。

参考地址:
http://caibaojian.com/es6/iterator.html
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Iterators_and_Generators
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Iterators_and_Generators
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/function*#%E6%8F%8F%E8%BF%B0
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/yield
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/yield*

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值