-
Notifications
You must be signed in to change notification settings - Fork 0
Description
一、背景:Iterator 迭代器
什么是迭代器呢?参考维基百科的解释:
迭代器(Iterator)是确使用户可在容器对象(container,例如链表或数组)上遍历的对象,设计人员使用此接口无需关心容器对象的内存分配的实现细节。
其主要用途是允许用户处理一个容器的所有元素,而将用户隔离于容器的内部结构。例如在 JavaScript 中,我们可以对以下实现了迭代器接口的任意对象使用 for...of 遍历其全部元素,而无需关心它具体是数组,还是 Map/Set/String:
- Array
- TypedArray
- String
- Map
- Set
for (let i of [0, 1, 2]) {
console.log(i); // 0 1 2
}
for (let s of 'hello') {
console.log(s); // h e l l o
}
for (let element of new Set([3, 4, 5]) ){
console.log(element); // 3 4 5
}以上是 JS 中内置了迭代器接口的几种集合(容器对象),他们能够被迭代的原因是这些数据结构的 [Symbol.iterator] 属性上都是函数,执行后会返回一个迭代器:
const arr = [0, 1, 2];
const func = arr[Symbol.iterator]; // ƒ values() { [native code] }
const it = arr[Symbol.iterator](); ; // Array Iterator {}返回的这个迭代器的作用是什么呢?我们发现,可以通过一直调用迭代器的 next 函数,来遍历它关联集合的全部元素:
it.next(); // {value: 0, done: false}
it.next(); // {value: 1, done: false}
it.next(); // {value: 2, done: false}
it.next(); // {value: undefined, done: true}当我们对 JS 中默认可迭代的数据结构进行 for...of 循环操作时,实际上也是在隐式生成相应迭代器来进行遍历。
同时注意到,迭代器调用 next 的返回值里面,除了有 value 属性表示当前迭代元素的值之外,还有一个 done 属性表示当前迭代器是否被消耗完毕,符合迭代器协议。迭代器协议对迭代器的行为是这样规定的:
一个迭代器需要实现一个拥有以下语义(semantic)的 next() 方法:
next() 为一个无参数函数,执行后必须返回一个对象,该对象应当有两个属性: done(boolean),value:任何 JavaScript 值,done 为 true 时可省略。
done 的判定:如果迭代器可以产生序列中的下一个值,则为 false。如果迭代器已将序列迭代完毕,则为 true。这种情况下,value 是可选的,如果它依然存在,即为迭代结束之后的默认返回值。
JS 在默认的可迭代数据结构中都内置了迭代器的实现,但我们也可以自行根据迭代器协议来生成各种自定义集合。比如可以这样得到一个指定范围内按指定梯度递增的队列:
function makeRangeIterator(start = 0, end = Infinity, interval = 1) {
let index = start;
return {
next: function() {
const res = index;
index+= interval;
if (res < end) {
return {
value: res,
done: false
};
} else {
return {
done: true
}
}
}
};
}
let it = makeRangeIterator(1, 10, 2);
let result = it.next();
while (!result.done) {
console.log(result.value); // 1 3 5 7 9
result = it.next();
}二、Generator 生成器
通过上面的例子发现,虽然可以使用普通函数生成自定义的迭代器,非常强大&灵活,但由于需要显示维护迭代器内部的状态,这种方式并不十分友好。为此,ES6 提供了一种专门用于生成迭代器的函数:生成器函数,它允许开发者编写自定义的迭代算法,而无需手动控制 next 的返回、 value, done 等状态,而是用一种新的语法和关键字自动完成 next 函数的定义及 value 和 done 的返回,编写者只需要控制其中的迭代逻辑。生成器函数使用 function* 的语法编写:
function* makeRangeIterator(start = 0, end = Infinity, step = 1) {
for (let i = start; i < end; i += step) {
yield i;
}
return 444;
}最初被调用时,生成器函数不执行任何代码,而是返回一个迭代器:
let it = makeRangeIterator(1, 10, 2); // makeRangeIterator {<suspended>}
console.log(it.next); // ƒ next() { [native code] }当第一次调用这个迭代器的 next 方法时,其返回值取决于生成器中遇到的第一个 yield语句, 生成器中的代码将会执行,直到执行完第一个 yield expression 语句,此时生成器函数会暂停并退出,同时会返回一个具有value, done 属性的对象(符合迭代器协议),其中 value 的值为 yield 关键字后面 expression 的计算值
it.next(); // {value: 1, done: false}当继续调用迭代器的 next方法时,生成器函数会从刚才暂停的地方继续执行,直到遇到下一个 yield 语句。如果继续执行后发现没有 yield 语句,则会返回 { value: undefined, done: true }(如果生成器函数有 return 语句,则下一次的 value 为 return 表达式的值)
it = makeRangeIterator(1, 10, 2);
let result = it.next();
while (!result.done) {
console.log(result.value); // 1 3 5 7 9
result = it.next();
}
// 之后再调用 next 会一直返回 { value: undefined , done: true }
// 无论生成器函数中是否有 return 语句
it.next(); // {value: undefined , done: true} 三、迭代器与可迭代对象
一对比较容易混淆但又常在一起出现的概念是:迭代器与可迭代对象。一句话解释这两个的关系就是:可迭代对象是 [Symbole.iterator] 属性上实现了 next 方法(且返回值遵循迭代器协议)的对象,