一、迭代器

迭代就是遍历一个东西,不一定要遍历完。


为什么需要迭代器?因为使用循环来迭代不理想,具体有两方面原因:

  • 迭代之前需要事先知道如何使用数据结构。如利用数组下标[]来取得特定索引位置上的项
  • 遍历顺序并不是数据结构固有的。如通过递增索引来访问数据,只有数组能这样

我们需要的是一种通用迭代方式,能对自己定义的任何对象进行迭代的方法。这个方法就是使用迭代器


这里需要分清三个概念:可迭代对象、迭代器工厂函数、迭代器:

let str = ['a','b','c']; // 可迭代对象
console.log(str[Symbol.iterator]) // 打印出迭代器工厂函数
console.log(str[Symbol.iterator()]) // 调用迭代器工厂函数会生成一个迭代器

 

实现迭代器接口意味着该可迭代对象有个 Symbol.iterator 键(作为默认迭代器属性),它引用一个迭代器工厂函数,调用这个工厂函数必须返回一个新迭代器。JS中实现迭代器接口的对象有 String Array Map Set DOM, 这些对象在如 for - of 等语法下会自动调用迭代器工厂函数创建迭代器,然后调用迭代器的 next 方法,返回一个对象包含 done 和 value:

class Counter{
        constructor(limit){
            this.limit=limit;
        }

        // 这就叫实现了迭代器接口,它返回一个迭代器对象,next是其方法
        [Symbol.iterator](){
            let count=1,
                limit=this.limit;
            return {
                next(){
                    if(count<=limit){
                        return {done:false,value:count++};  // 若迭代还没完
                    }else{
                        return {done:true,value:undefined};  // 若迭代结束
                    }
                }
            }
        }
    }

    let counter = new Counter(3);

    for(let i of counter){console.log(i)} // 1 2 3
    for(let i of counter){console.log(i)} // 1 2 3

return() 方法可以用来提前关闭迭代器,当 for-of 结构遇到 break、continue、return 、throw 提前退出:

class Counter{
        constructor(limit){
            this.limit=limit;
        }

        // 这就叫实现了迭代器接口,它返回一个迭代器对象,next是其方法
        [Symbol.iterator](){
            let count=1,
                limit=this.limit;
            return {
                next(){
                    if(count<=limit){
                        return {done:false,value:count++};
                    }else{
                        return {done:true,value:undefined};
                    }
                },
                return(){
                    console.log('提前退出');
                    return {done:true};
                }
            }
        }
    }

    let counter = new Counter(3);

    for(let i of counter){
        if(i>2){
            break;
        }
        console.log(i);  
    }
    // 1 2 提前退出

如果迭代器没有关闭,还可以继续从上次离开的地方开始迭代:

let a = [1,2,3,4,5];
    let iterator = a[Symbol.iterator](); // 返回迭代器

    for(let i of iterator){
        console.log(i);
        if(i>2){
            break;
        }
    }
    // 1 2 3
    for(let i of iterator){
        console.log(i);
    }
    // 4 5

数组的迭代器是不能关闭的,即使给它增加一个 return 方法也不行。

 

 

二、生成器

使用生成器可以拥有一个函数块内暂停和恢复代码执行的能力。比如实现生成器可以自定义迭代器实现协程... 调用生成器函数会产生一个生成器对象。生成器对象一开始处于暂停执行的状态,生成器对象也实现了 Iterator 接口,因此具有next方法。调用这个方法会让生成器开始或恢复执行:

    // 生成器函数
    function *generatorFn(){
        console.log('foobar');
    }
    let generatorObj = generatorFn() // 返回生成器对象,不会执行函数
    generatorObj.next() // 调用next()方法开始执行,打印出foobar

 

yield 关键字可以让生成器停止执行,next方法可以让生成器恢复执行。yield 关键字类似中间返回语句,通过 yield 关键字退出的生成器函数会处在 done: false 状态,通过 return 关键字退出的生成器函数

会处于 done:true 状态:

// 生成器函数
    function *generatorFn(){
        yield '1';
        yield '2';
        return '3';
    }
    let generatorObj = generatorFn() // 返回生成器对象,不会执行函数
    generatorObj.next() // { done:false,value:'1' }
    generatorObj.next() // { done:false,value:'2' }
    generatorObj.next() // { done:true,value:'3' }

 

生成器作用:

  • 可以用来作为可迭代对象(因生成器对象实现了迭代器接口),注意下面代码中for循环调用了生成器函数,返回了生成器对象,for-of会自动调用其迭代器方法next:
function *generatorFn(){
        yield '1';
        yield '2';
        yield '3';
    }

    for(const x of generatorFn()){
        console.log(x)  // 1 2 3
    }

上面代码也可改为 yield 加星号*,它可以将可迭代对象序列化一连串可以单独产出的值:

// 生成器函数
function *generatorFn(){
    yield* [1,2,3];
}

for(const x of generatorFn()){
    console.log(x)  // 1 2 3
}

 

  • 生成器作为默认迭代器

因为生成器对象实现了迭代器接口,因此可以作为默认迭代器,下面是一个例子,使用 for-of 会调用 f 对象的 [Symbol.iterator] 函数,返回一个生成器对象

class Foo{
    connstructor(){
        this.values=[1,2,3];
    }

    *[Symbol.iterator](){
        yield* this.values;
    }
}

const f = new Foo();

for(const x of f){
    console.log(x);  // 1 2 3
}

 

和迭代器一样,生成器也可以提前终止。它默认有两个方法: return() 和 throw()  ,调用这两个方法都可以强制生成器进入关闭状态。提供给 return() 方法的参数,就是终止迭代器对象的值:

function *generatorFn(){
    for(const x of [1,2,3]){
        yield x;
    }
}
const generatorObj = generatorFn();
console.log(generatorObj.next()); // {done:false,value:1}
console.log(generatorObj.return()); // {done:true,value:4}
// 关闭之后再次调用 next
console.log(generatorObj.next()); /// {done:true,value:undefined}

for-of 循环会忽略状态为 done:true 的内部的 value。

 

throw方法会在往生成器对象中注入一个错误,如果错误未被处理,生成器就会关闭,若生成器函数内部进行了处理,那么生成器就不会被关闭,而且还可以恢复执行:

// 生成器函数内部没有进行错误处理:
function *generatorFn(){
    for(const x of [1,2,3]){
        yield x;
    }
}
const generatorObj = generatorFn();

try{
    g.throw('foo');
}catch(e){
    console.log(e) // foo
}
console.log(generatorObj) // 生成器对象此时被关闭

// 生成器函数内部进行了错误处理:
function *generatorFn(){
    for(const x of [1,2,3]){
        try{
            yield x;
        }catch(e){}
    }
}

const generatorObj = generatorFn();
console.log(generatorObj.next()) // {done:false,value:1}
generatorObj.throw('error');
console.log(generatorObj.next()) // {done:false,value:3}

 

posted on 2021-06-11 23:07  BillGates--  阅读(110)  评论(0)    收藏  举报