迭代器与生成器
1.迭代器
1.1传统迭代的缺陷
const arr = [1, 2, 3];
for(let i = 0; i < arr.length; i++){
console.log(arr[i]);
}
- 迭代前需要直到数据结构,如数组的 [ ] 操作不适合所有数据结构
- 数据结构本身可能不支持顺序遍历,如顺序为隐式顺序
注:Array.prototype.forEach() 虽然可达到迭代目的,但不能停止
1.2迭代器模式
好处是无需知道被迭代对象的结构即可取得值。
1.3可迭代协议
如果要实现Iterable接口需做到两点:
- 必须有以Symbol.iterator为键的属性
- 该属性执行后返回一个新迭代器
字符串、数组、映射、集合、arguments对象、NodeList等DOM集合类型都实现了Iterable接口
写代码时也不需要显式调用工厂函数,js有一些可接收可迭代对象的原生语言特性:
let arr = [1, 2, 3];
// for...of
for(const i of arr){
console.log(i);
}
// 数组结构
let [a, b, c] = arr;
// 拓展操作符
let arr2 = [...arr];
// Array.from()
let arr3 = Array.from(arr);
// Set
let set = new Set(arr);
// Map
let arr4 = arr.map((e, i) => [i, e]);
let map = new Map(arr4);
// Map(3) {0 => 1, 1 => 2, 2 => 3}
注:如果一个类是这些类的子类,那么也支持迭代
1.4迭代器协议
迭代器中有一个next()方法,每次调用都返回一个IterableResult对象,包含下一个返回的值。
IterableResult有两个属性 done和value:
- done:当还有值未迭代时未false,否则为false
- value:携带返回的值
// 可迭代对象
let arr = ['foo', 'bar'];
// 迭代器工厂函数
console.log(arr[Symbol.iterator]); // f values(){ [native code] }
// 迭代器
let iter = arr[Symbol.iterator]();
console.log(iter); // ArrayIterator {}
// 执行迭代
console.log(iter.next()); // { done: false, value: 'foo' }
console.log(iter.next()); // { done: false, value: 'bar' }
console.log(iter.next()); // { done: true, value: undefined }
console.log(iter.next()); // { done: true, value: undefined } 后面一直为true
迭代器不知道迭代何时结束,只管done为true
迭代器有以下特点:
- 迭代器是一次性的
- 不同的迭代器之间相互独立
- 迭代器不与对象的快照绑定,而是实时变化的(保持着引用)
1.5自定义迭代器
class A{
constructor(limit){
this.limit = limit;
}
[Symbol.iterator](){
let count = 1,
limit = this.limit;
return {
next(){
if(count<=limit){
return { value:count++,done:false };
}else{
return{ value:undefined,done:true };
}
},
return(){
console.log("迭代中断");
return { done:true };
},
[Symbol.iterator](){
return this;
}
}
}
}
这里采用了迭代器自引用(让迭代器本身也可以被迭代),内置对象和生成器对象都是自引用的
1.6提前终止迭代器
当发生for...of 中执行了break、continue、return和throw,或解构未消费所有的值时会提前结束迭代。
可以在迭代器中添加一个return()方法提前关闭迭代器,这个函数只能返回{done: true}
return() {
console.log('Exiting early');
return { done: true };
}
不要尝试在原生结构中添加此方法,不能实现关闭。(自己的结构测试也没效果)
2.生成器
生成器是ES6新增的一个极为灵活的结构,拥有在一个函数块内暂停和恢复代码执行的能力。
// 生成器函数声明
function* generatorFn() {}
// 生成器函数表达式
let generatorFn = function* () {}
// 作为对象字面量方法的生成器函数
let foo = {
* generatorFn() {}
}
// 作为类实例方法的生成器函数
class Foo {
* generatorFn() {}
}
// 作为类静态方法的生成器函数
class Bar {
static * generatorFn() {}
}
注:无法使用箭头函数
- 执行生成器会返回一个迭代器对象
- 这个迭代器对象是自引用的
2.1yield
yield关键字可以让生成器停止和开始执行,遇到这个关键字后,执行会停止,函数作用域的状态会被保留。只能通过在生成器对象上调用next()方法来恢复执行
yield关键字类似函数的中的return语句,它生成的值会出现在next()方法返回的对象里。
通过yield关键字退出的生成器函数会处在done: false状态;通过return关键字退出的生成器函数会处于done: true状态。
function* generatorFn() {
yield 'foo';
yield 'bar';
return 'baz';
}
let generatorObject = generatorFn();
console.log(generatorObject.next()); // { done: false, value: 'foo' }
console.log(generatorObject.next()); // { done: false, value: 'bar' }
console.log(generatorObject.next()); // { done: true, value: 'baz' }
注:yield关键字只能出现在生成器函数内部
2.2 生成器应用
-
自定义迭代对象,控制循环次数
// 1.循环计数器 function* nTimes(n) { let i = 0; while(n--) { yield i++; } } for (let x of nTimes(3)) { console.log(x); } // 2.填充数组 function* zeroes(n) { while(n--) { yield 0; } } console.log(Array.from(zeroes(8))); // [0, 0, 0, 0, 0, 0, 0, 0] // 3.生成范围 function* range(start, end) { while(end > start) { yield start++; } }
-
yield 表达式的值:上一次让生成器函数暂停的yield关键字会接收到传给next()方法的值。(也就是说第一次调用next() 值传不过去)
function* generatorFn(initial) { console.log(initial); console.log(yield); console.log(yield); } let generatorObject = generatorFn('foo'); generatorObject.next('bar'); // foo 第一次无法传 generatorObject.next('baz'); // baz generatorObject.next('qux'); // qux
例二:
function* generatorFn() { return yield 'foo'; } let generatorObject = generatorFn(); console.log(generatorObject.next()); // { done: false, value: 'foo' } console.log(generatorObject.next('bar')); // { done: true, value: 'bar' }
由于return返回的是后面子句整体的值,因此yield表达式的值为next()传进去的值。
2.3 yield *
可以使用星号增强yield的行为,让它能够迭代一个可迭代对象,从而一次产出一个值:
function* generatorFn() {
yield* [1, 2, 3];
}
let generatorObject = generatorFn();
for (const x of generatorFn()) {
console.log(x);
}
// 1
// 2
// 3
其实与把yield放入for...of循环中没有不同
function* generatorFnA() {
for (const x of [1, 2, 3]) {
yield x;
}
}
yield*表达式的值是关联迭代器返回done: true时的value 属性。
内置类型在done: true时的value 一般为undefinded。如果是生成器迭代器,则为return返回的值
function* innerGeneratorFn() {
yield 'foo';
return 'bar';
}
function* outerGeneratorFn(genObj) {
console.log('iter value:', yield* innerGeneratorFn());
}
for (const x of outerGeneratorFn()) {
console.log('value:', x);
}
// value: foo
// iter value: bar
还可以用yield*优雅的表达递归算法:
function *nTime(n){
if(n>0){
yield *nTime(n-1);
yield n-1;
}
}
for(const i of nTime(3)){
console.log(i);
}
// 1 2 3
2.4生成器作为默认迭代器
class A{
constructor(){
this.values = [1,2,3];
}
*[Symbol.iterator](){
yield* this.values;
}
}
2.5提前终止迭代器
两种方法return()、throw() 略