ES6 学习笔记(十一)迭代器和生成器函数
1、前言
JavaScript提供了许多的方法来获取数组或者对象中的某个元素或者属性(迭代)。从以前的for循环到之后的filter、map再到后来的for...in和for...of的迭代机制。只要具有iterator接口的都可被迭代。
2、迭代器 Iterator
2.1 含义
迭代器(iterator)为各种数据结构,提供一个统一的、简便的访问接口,简单的说,迭代可以是数组或对象的遍历方式。它使得数据结构的成员能够按某种次序排列,如上面所说,只要部署 Iterator 接口,就可以完成遍历操作。即依次处理该数据结构的所有成员。
2.2 工作过程
- 首先,创建一个指针对象,指向当前数据结构的起始位置。也就是说,遍历器对象本质上,就是一个指针对象。
- 第一次调用指针对象的next方法,可以将指针指向数据结构的第一个成员
- 第二次调用指针对象的next方法,指针就指向数据结构的第二个成员,以此类推。
- 不断调用指针对象的next方法,直到它指向数据结构的结束位置
2.3 Symbol.iterator属性
2.3.1 简介
ES6 规定,默认的 Iterator 接口部署在数据结构的Symbol.iterator属性。也就是说,只要某种数据结构具有iterator接口,就是可遍历的。常见的具有这一特点的数据结构有:Array、Map、Set、String、TypedArray(类型化的数组)、函数的 arguments 对象、NodeList 对象
2.3.2 示例1:数组的iterator示例
let arr = [1, 2, 3]
let iter = arr[Symbol.iterator]()
console.log(iter.next());
console.log(iter.next());
console.log(iter.next());
console.log(iter.next());
运行结果:
{ value: 1, done: false }
{ value: 2, done: false }
{ value: 3, done: false }
{ value: undefined, done: true }
其中,上面的iter返回的是一个迭代器对象,由于arr数组只有3个元素,所以经过3次的next()调用之后,第四次指针指到尾部了,已经结束了,所以获取的value值为undefined。迭代已完成,所以返回done。
2.3.3 对象的Iterator
一个对象如果要具备可被for...of循环调用的 Iterator 接口,就必须在Symbol.iterator的属性上部署遍历器生成方法(原型链上的对象具有该方法也可)。
2.3.4 示例2 实现iterator接口的自定义类示例
class RangeIterator {
constructor(start, stop, step) {
this.value = start;
this.stop = stop;
this.step = step;
}
[Symbol.iterator]() {
return this;
}
next() {
let value = this.value;
if (value < this.stop) {
this.value += this.step;
return { done: false, value: value };
}
return { done: true, value: undefined };
}
}
function range(start, stop, step = 1) {
return new RangeIterator(start, stop, step);
}
for (let value of range(0, 9, 2)) {
console.log(value);
}
输出结果: 0 2 4 6 8
2.3.5 示例3 为对象添加iterator示例
// 给对象添加iterator接口
let obj = {
data: [1, 2, 3, 4, 5, 6],
[Symbol.iterator]() {
const self = this;
let index = 0;
return {
next() {
if (index < self.data.length) {
return {
value: self.data[index++],
done: false
}
} else {
return {
value: undefined,
done: true
}
}
}
}
}
}
let sum = 0
for (let d of obj) {
sum += d
}
console.log(sum);
输出结果: 21
3、生成器 Generator
3.1、含义
在前面的文章 中也提到过生成器,举个最简单的例子:
let gen = function* () {
yield 1;
yield 2;
yield 3;
}
for (let n of gen()) {
console.log(n);
}
很明显,生成器的函数的function后面有个*,函数中存在yield 关键字,在函数中,通过gen()进行函数调用并生成控制器,在这里是通过循环执行函数的。
再举个例子
let obj = {
name: "Tom",
age: 20,
*[Symbol.iterator]() {
yield this.name;
yield this.age;
}
}
console.log(obj[Symbol.iterator]().next());
console.log([...obj]);
for (let k of obj) {
console.log(k);
}
输出结果: { value: 'Tom', done: false } [ 'Tom', 20 ] Tom 20
可以看到,Generator 函数返回的遍历器对象,只有调用next方法才会遍历下一个内部状态,所以其实提供了一种可以暂停执行的函数。yield表达式就是暂停标志。
3.2 工作过程
遍历器对象的next方法的运行逻辑:
- 遇到yield表达式,就暂停执行后面的操作,并将紧跟在yield后面的那个表达式的值,作为返回的对象的value属性值。
- 下一次调用next方法时,再继续往下执行,直到遇到下一个yield表达式。
- 如果没有再遇到新的yield表达式,就一直运行到函数结束,直到return语句为止,并将return语句后面的表达式的值,作为返回的对象的value属性值。
- 如果该函数没有return语句,则返回的对象的value属性值为undefined。
3.3 Generator 函数的return方法
举个例子:
// 生成器函数
let gen = function* () {
yield 1;
yield 2;
yield 3;
return 'ok'
}
let g = gen()
console.log(g.next());
console.log(g.next());
console.log(g.next());
console.log(g.next());
console.log(g.next());
输出结果: { value: 1, done: false } { value: 2, done: false } { value: 3, done: false } { value: 'ok', done: true } { value: undefined, done: true }
由于在遍历到第三个next()时循环就已经结束了。再次遍历返回OK,done为true,再次遍历,由于指针已经到队列末尾,所以值为undefined。
如果for...of循环提前退出(通常是因为出错,或者有break语句),就会调用return方法。如果一个对象在完成遍历前,需要清理或释放资源,就可以部署return方法。
3.4 示例:定义类Fib,实现Fibonacci数列的获取
Fibonacci数列的后一个数等于前两个数之和
// 斐波拉契数列 class Fib { constructor(num) { this.num = num } *[Symbol.iterator]() { let [a, b] = [0, 1] while (true) { [a, b] = [b, a + b] if (a > this.num) return yield a } } }
let fibs = new Fib(100)
for (let f of fibs) {
console.log(f);
}
输出结果: 1 1 2 3 5 8 13 21 34 55 89