JavaScript高级--迭代器和生成器
迭代器
- 迭代器(iterator)是确使用户可在容器对象(如链表、数组)上进行遍历访问的对象,使用该接口无需关心对象的内部实现细节
- 迭代器的行为像数据库中的光标,最早出现在 1974 年设计的
CLU
编程语言中,在现代编程语言(如Java
、JavaScript
等)的实现中,迭代器的实现方式各不相同 - 在
JavaScript
中,迭代器是一个具体的对象,该对象需要符合迭代器协议(iterator protocol) - 迭代器协议: 定义了产生一系列值的标准方式,在
JavaScript
中的标准就是特定的next
方法
标准的
next
函数要求:一个无参数函数,返回拥有以下两个属性的对象
-
done(boolean):
- 若迭代器可以产生序列中的下一个值则为
false
- 若迭代器已将序列迭代完毕则为
true
,这种情况下value
是可选的,如果它依然存在,即为迭代结束之后的默认返回值
- 若迭代器可以产生序列中的下一个值则为
-
value: 迭代器返回的任何
JavaScript
值,done
属性为true
时可省略
const iterator = {
next() {
return { done: false, value: 123 };
}
};
- 举个例子: 创建一个迭代器访问数组内容
const names = ['Jimmy', 'James', 'Joy'];
let index = 0;
const nameIterator = {
next() {
if (index < names.length) {
return { done: false, value: names[index++] };
} else {
return { done: true, value: undefined };
}
}
};
nameIterator.next(); // { done: false, value: Jimmy }
nameIterator.next(); // { done: false, value: James }
nameIterator.next(); // { done: false, value: Joy }
nameIterator.next(); // { done: true, value: undefined }
nameIterator.next(); // { done: true, value: undefined }
创建一个生成迭代器的函数
- 如果对于每个容器对象都写一次对应的迭代器,那么就会造成很多代码重复
- 可以针对某种容器对象(如数组)创建一个生成迭代器的函数
function creaetArrayIterator(arr) {
let index = 0;
return {
next() {
if (index < arr.length) {
return { done: false, value: arr[index++] };
} else {
return { done: true, value: undefined };
}
}
};
}
// 使用函数生成对应的迭代器
const namesIterator = creaetArrayIterator(['Jimmy', 'James', 'Joy']);
const numsIterator = creaetArrayIterator([10, 20, 30]);
可迭代对象
可迭代对象和迭代器是不同的概念
- 当一个对象实现了可迭代协议(iterable protocol)时,它就是一个可迭代对象
- 可迭代对象要求必须实现
@@iterator
方法,在代码中使用Symbol.iterator
访问该属性
const iterableObj = {
[Symbol.iterator]: function(){
return iterator;
}
}
- 使用可迭代对象生成一个迭代器,将相关的部分都耦合在一个可迭代对象中
const iterableObj = {
names: ['Jimmy', 'James', 'Joy'],
[Symbol.iterator]: function () {
let index = 0;
return {
next: () => {
if (index < this.names.length) {
return { done: false, value: this.names[index++] };
} else {
return { done: true, value: undefined };
}
}
};
}
};
const iterator = iterableObj[Symbol.iterator]();
iterator.next(); // { done: false, value: Jimmy }
iterator.next(); // { done: false, value: James }
iterator.next(); // { done: false, value: Joy }
iterator.next(); // { done: true, value: undefined }
- 以上方式在每次调用
iterableObj[Symbol.iterator]()
时,都会生成一个新的迭代器
for...of
与可迭代对象的关联
- 当使用
for...of
进行遍历时,遍历的对象必须是可迭代对象,普通对象则不行
// 这种用法则会报错
const obj = { a: 1, b: 2 };
for (const item of obj) {
// ...
}
- 那么可以自定义一个可迭代对象,提供于
for...of
遍历,其所遍历的结果是迭代器每次调用next()
返回的value
值 - 而
for...of
可以看成是一种语法糖,相当于iterator.next().value
,当done
属性为false
时,就取其value
值赋给item
(如下所示)
for (const item of iterableObj) {
console.log(item); // Jimmy, James, Joy;
}
迭代器的中断
迭代器可在没有完全迭代的情况下中断
- 遍历过程中使用
break
、continue
、return
、throw
等中断循环 - 在解构时,没有解构所有的值
监听迭代器的中断
- 迭代器中断时,会调用其内部实现的
return
方法
const iterableObj = {
names: ['Jimmy', 'James', 'Joy'],
[Symbol.iterator]() {
let index = 0;
return {
next: () => {
if (index < this.names.length) {
return { done: false, value: this.names[index++] };
} else {
return { done: true, value: undefined };
}
},
return: () => {
console.log('迭代器被中断了');
return { done: true, value: undefined };
}
};
}
};
// 当循环被break时,会触发迭代器的return方法
for (const value of iterableObj) {
if (value === 'James') break;
}
生成器
- 生成器是
ES6
中新增的一种函数控制、使用的方案,能灵活地控制函数的执行时机,它允许定义一个有迭代算法的函数,同时可以自动维护自身状态 - 生成器事实上是一种特殊的迭代器
执行流程对比
普通函数执行流程
- 普通函数的执行流程的同步的,由上往下依次执行内部代码,不可中断
function foo() {
const var1 = 100;
console.log(var1); // 100
const var2 = 200;
console.log(var2); // 200
}
foo();
console.log(300); // 300
- 如上所示,上面代码会先把函数内部逻辑执行完成,再执行最底下的
console.log
- 那么有没有办法,可以使得函数先输出
100
,然后暂停往下执行内部代码,而是待输出300
后,再回到函数内部执行剩余代码,接着输出200
生成器函数执行流程
- 利用生成器实现对函数的执行时机进行控制
function* generatorFoo() {
const var1 = 100;
console.log(var1);
yield; // 函数暂停
const var2 = 200;
console.log(var2);
}
const generator = generatorFoo(); // 初次调用函数不执行,而是返回一个Geneator对象
generator.next(); // 函数执行,输出100
console.log(300);
generator.next(); // 函数执行,输出200
- 如上所示,当使用生成器函数时,函数内部的执行逻辑可以被暂停,进而把控制权交到函数外部,执行其他代码,所以最后的输出顺序就会是
100、300、200
对比普通函数
生成器函数和普通函数的区别
- ①生成器函数需要在
function
关键字后加一个符号*
- ②生成器函数可通过
yield
关键字来控制函数的执行流程 - ③生成器函数在最初调用时不执行任何代码,而是返回一个名为
Generator
的特殊迭代器,需通过调用生成器的next
方法时,生成器函数才执行,直到遇到yield
关键字则暂停
执行过程
- 如下代码所示,它的执行顺序并不像普通函数一样,从上往下依次执行,而是通过
next
方法和yield
关键字相互配合,对generatorFoo
函数进行控制
function* generatorFoo() {
const var1 = 100;
console.log(var1);
yield;
const var2 = 200;
console.log(var2);
return ’123’
}
const generator = generatorFoo();
const res1 = generator.next();
console.log(300);
const res2 = generator.next();
- 首先调用
generatorFoo()
函数,但函数内部代码并不执行,而是返回一个名为Generator
的特殊迭代器
const generator = generatorFoo(); // 调用函数
- 调用
generator.next()
方法,执行函数内部代码
const res1 = generator.next(); // 函数执行
- 函数内部代码执行后,遇到
yeild
语句,函数暂停,并且会返回一个对象
返回的对象和迭代器中的形式一致,如
{ value: xxx, done: false }
value
值取决于yield
语句后的内容,如yield 123;
则value
值为 123,相当于不中断的return
done
值取决于函数内部代码是否已执行完毕,是则为true
,反之为false
const var1 = 100;
console.log(var1); // 输出100
yield; // 遇到yield,函数暂停,并返回 { value: undefined, done: false }
// res1 = { value: undefined, done: false }
- 函数暂停,继续执行全局代码
console.log(300); // 输出300
- 继续调用
generator.next()
方法,返回函数中,继续从上次暂停的yield
开始往下执行
const res2 = generator.next(); // 函数执行
- 执行函数内后面的代码,当执行结束后,同样返回一个对象
这次对象内容则为
{ value: 123, done: true }
value
值为 123 是因为函数最后return
了 123,return
相当于一个特殊的yield
语句done
值为true
是因为函数已经return
,表示执行结束
const var2 = 200;
console.log(var2); // 输出200
return 123
// res1 = { value: 123, done: true }
总结
- 生成器是一种特殊的迭代器
- 生成器函数当遇到
yield
语句时,函数暂停;当遇到return
语句时,生成器停止
其他方法
next
方法传递参数
- 生成器的
next
方法是可以传参数的,如下代码所示,当第二次调用next
函数时传入参数
function* generatorFoo() {
yield;
const var2 = 200;
console.log(var2);
}
const generator = generatorFoo();
generator.next();
generator.next(10);
next
方法传入的参数,会被上一个yield
语句所接收,如下所示
function* generatorFoo() {
const num = yield; // 接收第二次next传入的参数10
const var2 = 200 * num
console.log(var2); // 2100
}
const generator = generatorFoo();
generator.next();
generator.next(10);
- 注意: 第一次调用
next
传参是无意义的,因为没有yield
语句接收,若想接收参数可在调用生成器函数generatorFoo
时传入
function* generatorFoo(num) {
yield;
const var2 = 200 * num;
console.log(var2);
}
const generator = generatorFoo(10);
generator.next(); // { value: 2000, done: true }
return
方法终止生成器
- 调用生成器的
return
方法可以提前结束生成器,而函数后面代码不会继续执行
function* generatorFoo() {
const var1 = 100;
console.log(var1);
const num = yield var1;
const var2 = 200 * num;
console.log(var2);
yield var2;
console.log('end');
}
const generator = generatorFoo();
generator.next();
generator.return(10);
- 如上所示,当调用
generator.next()
时,函数执行,输出 100,遇到yield
语句暂停 - 当调用
generator.return(10)
时,生成器终止,函数后面代码都不会执行
function* generatorFoo() {
...
const num = yield var1;
// 以下代码都不会执行
const var2 = 200 * num;
console.log(var2);
yield var2;
console.log('end');
}
- 此时
generator.return(10)
的返回值是{ value: 10, done: true }
,相当于在未执行的代码前加上了一个return
语句
function* generatorFoo() {
...
const num = yield var1;
return num; // 调用return方法相当于加上一个return语句,而return传入的参数被上一个yield接收
// 以下代码都不会执行
const var2 = 200 * num;
console.log(var2);
yield var2;
console.log('end');
}
throw
方法抛出异常
- 生成器的
throw
方法也会终止生成器,与return
不同的是,调用throw
方法,上一个yield
语句会抛出异常
function* generatorFoo() {
const var1 = 100;
console.log(var1);
try {
yield var1;
} catch (e) {
console.log('捕获到异常:', e);
}
const var2 = 200;
console.log(var2);
yield var2;
console.log('end');
}
const generator = generatorFoo();
generator.next();
generator.throw();
yield
语句抛出的异常可以被捕获,捕获异常后可以继续执行函数的后面代码- 若不捕获
yield
语句的异常,生成器则会终止
替代迭代器
- 既然生成器是一种特殊的迭代器,那么在某些情况下,可以使用生成器来替代迭代器
- 之前使用迭代器,对某个容器对象进行迭代,都需要定义
index
和next
方法
function creaetArrayIterator(arr) {
let index = 0;
return {
next() {
if (index < arr.length) {
return { done: false, value: arr[index++] };
} else {
return { done: true, value: undefined };
}
}
};
}
- 可以使用生成器函数进行替代,然后调用其返回的
Generator
对象的next
方法
function* createArrayIterator(arr) {
for (const item of arr) {
yield item;
}
}
- 事实上还可以使用
yield*
语句来生产一个可迭代对象,yield*
相当于一种语法糖,它会依次迭代这个可迭代对象,每次迭代其中的一个值
function* createArrayIterator(arr) {
yield* arr;
}
const namesIterator = createArrayIterator(['Jimmy', 'James', 'Joy']);
namesIterator.next(); // { value: 'Jimmy', done: false }
namesIterator.next(); // { value: 'James', done: false }
namesIterator.next(); // { value: 'Joy', done: false }
namesIterator.next(); // { value: undefined, done: true }
转载自:https://juejin.cn/post/7281535380590542859