JS中迭代器的介绍

1、简介

  • 迭代器(iterator),使用户在容器对象(container,例如链表或数组)上遍访的对象,使用该接口无需关心对象的内部实现细节。
  • 迭代器(iterator)是一个结构化的模式,用于从源以一次一个的方式提取数据。
  • 迭代器是一种有序的、连续的、基于拉取的用于消耗数据的组织方式。

2、接口

Iterator [required]
	next() {method}: 取得下一个IteratorResult

有些迭代器还扩展支持两个可选成员:

Iterator [optional]
	return() {method}: 停止迭代器并返回IteratorResult
	throw() {method}: 报错并返回IteratorResult

IteratorResult 接口指定如下:

IteratorResult
	value {property}: 当前迭代值或者最终返回值(如果undefined为可选的)
	done {property}: 布尔值,指示完成状态

还有一个 Iterable 接口,用来表述必需能够提供生成器的对象:

Iterable
	@@iterator() {method}: 产生一个 Iterator

3、next()迭代

我们来观察一个数组,这是一个 iterable,它产生的迭代器可以消耗其自身值:

var arr = [1,2,3];
var it = arr[Symbol.iterator]();
it.next(); // { value: 1, done: false }
it.next(); // { value: 2, done: false }
it.next(); // { value: 3, done: false }
it.next(); // { value: undefined, done: true }

前面代码中在提取值 3 的时候,迭代器 it 不会报告 done: true。必须得再次调用 next(),越过数组结尾的值,才能得到完成信号 done: true。虽然直到本小节的后面才会介绍原因,但这样的设计决策通常被认为是最佳实践。

基本字符串值默认也可以迭代:

var greeting = "hello world";
var it = greeting[Symbol.iterator]();
it.next(); // { value: "h", done: false }
it.next(); // { value: "e", done: false }
..

严格来说,基本值本身不是 iterable,但是感谢“封箱”技术,"hello world"被强制转换 / 变换为 String 对象封装形式,而这是一个 iterable

ES6中还包括几个新的称为集合的数据结构。这些集合不仅本身是iterable,还提供了 API 方法来产生迭代器,比如:

var m = new Map();
m.set( "foo", 42 );
m.set( { cool: true }, "hello world" );
var it1 = m[Symbol.iterator]();
var it2 = m.entries();
it1.next(); // { value: [ "foo", 42 ], done: false }
it2.next(); // { value: [ "foo", 42 ], done: false }
..

4、可选的return(…)和throw(…)

return(…) 被定义为向迭代器发送一个信号,表明消费者代码已经完毕,不会再从其中提取任何值。这个信号可以用于通知生产者(响应 next(…) 调用的迭代器)执行可能需要的清理工作,比如释放 / 关闭网络、数据库或者文件句柄资源。

如果迭代器存在 return(…),并且出现了任何可以自动被解释为异常或者对迭代器消耗的提前终止的条件,就会自动调用 return(…)。你也可以手动调用 return(…)。

return(…) 就 像 next(…) 一样会返回一个IteratorResult 对 象。 一般来说,发送 return(…) 的可选值将会在这个 IteratorResult 中作为 value 返回,但在一些微妙的情况下并非如此。

throw(…) 用于向迭代器报告一个异常 / 错误,迭代器针对这个信号的反应可能不同于针对return(…) 意味着的完成信号。和对于 return(…) 的反应不一样,它并不一定意味着迭代器的完全停止。

例如,通过生成器迭代器,throw(…) 实际上向生成器的停滞执行上下文中插入了一个抛出的异常,这个异常可以用 try…catch 捕获。未捕获的 throw(…) 异常最终会异常终止生成器迭代器。

4、迭代器循环

如果一个迭代器也是一个 iterable,那么它可以直接用于 for…of 循环。你可以通过为迭代器提供一个 Symbol.iterator 方法简单返回这个迭代器本身使它成为 iterable:

var it = {
	// 使迭代器it成为iterable
	[Symbol.iterator]() { return this; },
	next() { .. },
	..
};
it[Symbol.iterator]() === it; // true

现在可以用 for…of 循环消耗这个 it 迭代器:

for (var v of it) {
	console.log( v );
}

要彻底理解这样的循环如何工作,可以看一下 for…of 循环的等价 for 形式:

for (var v, res; (res = it.next()) && !res.done; ) {
	v = res.value;
	console.log( v );
}

5、自定义迭代器

除了标准的内置迭代器,你也可以构造自己的迭代器!要使得它们能够与 ES6 的消费者工具(比如,for…of 循环以及… 运算符)互操作,所需要做的就是使其遵循适当的接口。让我们试着构造一个迭代器来产生一个无限斐波纳契序列:

var Fib = {
	[Symbol.iterator]() {
		var n1 = 1, n2 = 1;
		return {
			// 使迭代器成为iterable
			[Symbol.iterator]() { return this; },
			next() {
				var current = n2;
				n2 = n1;
				n1 = n1 + current;
				return { value: current, done: false };
			},
			return(v) {
				console.log("Fibonacci sequence abandoned.");
				return { value: v, done: true };
			}
		};
	}
};
for (var v of Fib) {
	console.log( v );
	if (v > 50) break;
}
// 1 1 2 3 5 8 13 21 34 55
// Fibonacci sequence abandoned.

调用 FibSymbol.iterator 方法的时候,会返回带有 next() 和 return(…) 方法的迭代器对象。通过放在闭包里的变量 n1 和 n2 维护状态。

6、迭代器消耗

前面已经展示了如何通过for…of循环一个接一个地消耗迭代器项目,但是还有其他ES6结构可以用来消耗迭代器。

var a = [1,2,3,4]
//spread运算符...完全消耗了迭代器。
function foo(x,y,z,w,p){
	console.log(x+y+z+w+p)
}
foo(...a)  //15
//...也可以把一个迭代器展开到一个数组中
var b = [0,...a,6]
b; //[0,1,2,3,4,5,6]

数组解构可以部分或完全消耗一个迭代器:

var it = a[Symbol.iterator]();
//从it中获取前两个元素
var [x,y] = it;
//获取第三个元素,然后一次取得其余所有元素
var [z,...w] = it
//it已将消耗完了吗?是的
it.next(); //{value:undefined,done:true}
x;//1
y;//2
z;//3
w;//[4,5]

你可能感兴趣的:(JavaScript,javascript,开发语言,ecmascript)