一、理解迭代
迭代的意思是按照顺序反复多次执行一段程序,通常会有明确的终止条件,ES6规范新增了两个高级特性:迭代器和生成器,使用这两个特性能够更清晰、高效、方便地实现迭代。
关于迭代
JavaScript中,计数循环就是一种最简单的迭代
{
for(let i = 0; i <= 10; i++){
console.log(i);
}
}
循环时迭代机制的基础,这是因为它可以指定迭代的次数以及每次迭代要执行什么操作,每次循环都会在下一次迭代开始之前完成,而每次迭代的顺序都是事先定义好的。
迭代会在一个有序集合上进行,数组就是最典型的有序集合
{
let arr = ['red','blue','green'];
for(let i of arr){
console.log(i); // red blue green
}
}
二、迭代器模式
迭代器模式是指可以把有些结构称为可迭代对象,因为它们实现了正式的Iterator接口,而且可以通过迭代器Iterator消费。
可迭代对象是一种抽象的说法,基本上可以把可迭代对象理解成数组或者集合这样的集合类型的对象,它们包含的元素都是有限的,而且都具有无歧义的遍历顺序。
任何实现Iterator接口的数据结构都可以被实现Iterator接口的结构消费,迭代器(iterator)是按需创建的一次性对象,每个迭代器都会关联一个可迭代对象。
可迭代协议
实现Iterator接口(可迭代协议)要求具备两种能力:支持迭代的自我识别能力和创建实现Iterator接口的对象的能力,这意味着必须暴露一个属性作为“默认迭代器”,而且这个属性必须使用特殊的Symbol.iterator作为键。
Iterator的作用
- 为各种数据结构提供一个统一的、简便的访问接口
- 使得数据结构的成员可以按照某种次序排列
- ES6提供了for…of循环来消费Iterator接口
- 任何数据结构只要具备Iterator接口,就可以完成遍历操作
Iterator的遍历过程
- 创建一个指针对象,指向当前数据结构的起始位置,也就是说遍历器对象本质上就是一个指针对象
- 第一次调用指针对象的next()方法,可以将指针指向数据结构的第一个成员
- 第二次调用指针对象的next方法,指针就指向数据结构的第二个成员
- 不断调用指针对象的next()方法,直到它指向数据结构的结束位置
Symbol.iterator属性
ES6规定,默认的Iterator接口部署在数据结构的Symbol.iterator属性,或者是一个数据结构只要具备Symbol.iterator属性就可以认为是可迭代的。
具备Iterator接口的数据结构
- 字符串
- 数组
- Map
- Set
- TypeArray(类型化的数组)
- arguments对象
- NodeList等DOM集合类型
{
let arr = ['red','blue','black'];
let iter = arr[Symbol.iterator]();
console.log(iter);
console.log(iter.next()); // { value: 'red', done: false }
console.log(iter.next()); // { value: 'blue', done: false }
console.log(iter.next()); // { value: 'black', done: false }
console.log(iter.next()); // { value: undefined, done: true }
}
通过创建迭代器并调用next()方法按顺序迭代了数组,直到不再产生新值,迭代器不知道怎么从可迭代对象中取得下一个值,只要迭代器到达done : true状态,后续再调用next就一直返回同样的值了。
为对象添加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
}
}
三、生成器
生成器是ES6新增的一个极为灵活的解构,拥有在一个函数块内暂停和恢复代码执行的能力,使用生成器可以自定义迭代器。
生成器的形式是一个函数,函数名称前面加一个*号表示它是一个生成器,只要是可以定义函数的地方就可以定义生成器。
{
// 生成器函数声明
function* generator(){}
// 生成器函数表达式
let generatorFn = function*(){}
// 作为对象字面量方法的生成器函数
let obj = {
*generation(){}
}
// 作为类实例方法的生成器函数
class Person{
*generation(){}
}
}
通过yield中断执行
yield关键字可以让生成器停止和开始执行,也是生成器最有用的地方,生成器函数在遇到yield关键字之前会正常执行,遇到这个关键字后,执行会停止,函数作用域的状态会被保留。停止执行的生成器函数只能通过在生成器对象上调用next()方法来恢复执行。
{
function *generation(){
yield;
}
let obj = generation();
console.log(obj.next()); // { value: undefined, done: false }
console.log(obj.next()); // { value: undefined, done: true }
}
通过Generator函数实现Iterator接口迭代
{
let obj = {
color:['blue','black','white'],
*[Symbol.iterator](){
let index = 0;
while(index<this.color.length){
yield this.color[index];
index++;
}
}
}
console.log([...obj]); // [ 'blue', 'black', 'white' ]
}
{
let obj = {
name1:"Tom",
name2:"Jack",
name3:"Marry",
*[Symbol.iterator](){
yield this.name1;
yield this.name2;
yield this.name3;
}
}
console.log(obj[Symbol.iterator]().next()); // { value: 'Tom', done: false }
console.log([...obj]); [ 'Tom', 'Jack', 'Marry' ]
for(let k of obj){
console.log(k); // Tom Jack Marry
}
}
由于Generator函数返回的是遍历器对象,因此只有调用next方法才会遍历下一个内部状态,还可以使用forof循环或者展开操作符等实现了可迭代协议的方法来生成迭代器
遍历器对象的next方法的运行逻辑
- 遇到yield表达式,就暂停执行后面的操作,并将紧跟在yield后面的那个表达式的值,作为返回的对象的value属性值
- 下一次调用next方法时,再继续往下执行,直到遇到下一个yield表达式
- 如果没有再遇到新的yield表达式,就一直运行到函数结束,直到return语句为止,并将return语句后面的表达式的值,作为返回的对象的value属性值
- 如果该函数没有return语句,则返回的对象的value属性值为undefined
遍历器对象除了具有next方法,还可以具有return方法
- 如果for…of循环提前退出(通常是因为出错,或者有break语句)就会调用return方法。如果一个对象在完成遍历前,需要清理或释放资源,就可以使用return方法
{
function *helloGenerator(){
yield 'hello';
yield 'world';
return 'ending';
}
let g = helloGenerator();
console.log(g.next());
console.log(g.next());
console.log(g.next());
console.log(g.next());
/*
{ value: 'hello', done: false }
{ value: 'world', done: false }
{ value: 'ending', done: true }
{ value: undefined, done: true }
*/
}
通过迭代器和生成器实现斐波那契数列
{
// 定义一个类
class Fib {
constructor(num) {
this.num = num;
}
*[Symbol.iterator]() {
let [a, b] = [0, 1];
while (true) {
// 交换a和b的值
[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);
}
}