迭代器(Iterator)
Iterator 的作用
- 为各种数据结构,提供一个统一的、简便的访问接口。
- 使得数据结构的成员能够按某种次序排列。
- ES6提供了新的遍历命令for…of循环来消费Iterator 接口。
- 任何数据结构只要部署 Iterator 接口,就可以完成遍历操作。(即依次处理该数据结构的所有成员。)
Iterator 的遍历过程
- 创建一个指针对象,指向当前数据结构的起始位置。也就是说,遍历器对象本质上,就是一个指针对象。
- 第一次调用指针对象的next方法,可以将指针指向数据结构的第一个成员。
- 第二次调用指针对象的next方法,指针就指向数据结构的第二个成员。
- 不断调用指针对象的next方法,直到它指向数据结构的结束位置。
Symbol.iterator属性
-
ES6 规定,默认的 Iterator 接口部署在数据结构的Symbol.iterator属性。
(或者说,一个数据结构只要具有Symbol.iterator属性,就可以认为是“可遍历的”(iterable)。) -
原生具备 Iterator 接口的数据结构
① Array
② Map
③ Set
④ String
⑤ TypedArray(类型化的数组)
⑥ 函数的 arguments 对象
⑦ NodeList 对象 -
一个对象如果要具备可被for…of循环调用的 Iterator 接口,就必须在Symbol.iterator的属性上部署遍历器生成方法(原型链上的对象具有该方法也可)。
实例
// 数组类的迭代器
let arr = ["a", "b", "c"];
let iter = arr[Symbol.iterator]();
console.log(iter.next()); // {value: "a", done: false}
console.log(iter.next()); // {value: "b", done: false}
console.log(iter.next()); // {value: "c", done: false}
console.log(iter.next()); // {value: undefined, done: true}
// 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 };
}
}
let 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;
}
// 为对象添加iterator接口
let obj = {
data: ["a", "b", "c"],
[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 };
}
},
};
},
};
for (let d of obj) {
console.log(d); // a b c;
}
console.log(obj[Symbol.iterator]); // [Function: [Symbol.iterator]]
生成器
通过Generator函数实现Iterator 接口迭代。
let obj = {
name1: "Tom",
name2: "Jerry",
name3: "Mickey",
name4: "Miney",
*[Symbol.iterator]() {
// yield this.name1;
// yield this.name2;
// yield this.name3;
// yield this.name4;
// 注释的代码可以优化成下面一个for循环
for (let i = 1; i < 5; i++) {
yield this["name" + i];
}
},
};
console.log(obj[Symbol.iterator]().next()); // { value: 'Tom', done: false }
console.log([...obj]); // [ 'Tom', 'Jerry', 'Mickey', 'Miney' ]
for (let k of obj) {
console.log(k); // Tom Jerry Mickey Miney;
}
由于 Generator 函数返回的遍历器对象,只有调用next方法才会遍历下一个内部状态,所以其实提供了一种可以暂停执行的函数。yield表达式就是暂停标志。
遍历器对象的next方法的运行逻辑
- 遇到yield表达式,就暂停执行后面的操作,并将紧跟在yield后面的那个表达式的值,作为返回的对象的value属性值。
- 下一次调用next方法时,再继续往下执行,直到遇到下一个yield表达式。
- 如果没有再遇到新的yield表达式,就一直运行到函数结束,直到return语句为止,并将return语句后面的表达式的值,作为返回的对象的value属性值。
- 如果该函数没有return语句,则返回的对象的value属性值为undefined。
遍历器对象除了具有next方法,还可以具有return方法。
- 如果for…of循环提前退出(通常是因为出错,或者有break语句),就会调用return方法。如果一个对象在完成遍历前,需要清理或释放资源,就可以部署return方法。
function* helloWorldGenerator() {
yield "hello";
yield "world";
return "ending";
}
let hw = helloWorldGenerator();
console.log(hw.next()); // { value: 'hello', done: false }
console.log(hw.next()); // { value: 'world', done: false }
console.log(hw.next()); // { value: 'ending', done: true }
console.log(hw.next()); // { value: undefined, done: true }
实例:用生成器实现斐波那契数列
class Fib {
constructor(num) {
// num限制了输出的斐波那契数列最后一项的数值大小
this.num = num;
}
// 这里用生成器对斐波那契数列进行输出
*[Symbol.iterator]() {
// 用解构赋值对数列第一、二项进行赋值
let [a, b] = [0, 1];
// 用while死循环进行遍历
while (true) {
// 斐波那契数列:该项等于前两项相加的和
[a, b] = [b, a + b];
// 当该项数值大于指定的num数值时,退出循环
if (a > this.num) return;
// 返回此时a的值,即小于num的最后一个数
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
}
// 该类不足:由于num形参在类外部是暴露出来的,在调用时,可以在外部对num进行修改