Js 迭代器与生成器

迭代器与生成器


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接口需做到两点:

  1. 必须有以Symbol.iterator为键的属性
  2. 该属性执行后返回一个新迭代器

字符串、数组、映射、集合、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. 迭代器是一次性的
  2. 不同的迭代器之间相互独立
  3. 迭代器不与对象的快照绑定,而是实时变化的(保持着引用)

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() 略

posted @ 2021-05-20 23:24  王子饼干  阅读(123)  评论(0编辑  收藏  举报