js中的迭代器iterator和生成器Generator

329 阅读3分钟

时间:2021-10-8
作者:小明
掘金第一篇文章 js中有for...of..这个方法,像我一开始接触的时候就经常把他跟for...in...搞混,一搞混当我们遍历对象的时候就会提示你下面这个错误

Uncaught TypeError: a is not iterable at <anonymous>:1:16

这里会说对象是不可迭代的,原来只有可迭代对象才能使用这个方式来遍历,那么什么是可迭代对象呢,这就要说到可迭代协议了,如下图:

企业微信20210928-185152@2x.png 要成为一个可迭代对象必须要实现@@iterator方法,很显然我们普通声明的一个对象是没有实现的, 我们声明一个数组,查看他的原型对象上可以看到Symbol.iterator属性,

const arr=[1,2,3]
arr.__proto__

企业微信20210928-185651@2x.png 这就说明js的内置数组是默认实现了他的迭代方法的,所以数组是可以通过for...of..这个方式来进行迭代的,同理我们的set、map也是默认就实现了迭代方法的,那么我们如果想让我们的对象也可以通过for of来遍历呢,我就经常把for...of..和for...in.搞混..我们来看迭代器的定义,如下图:

image.png 从上图可以看到迭代器是一个对象,且这个对象有一个next方法,方法返回value和done两个值,那么我们就可以来实现一个这个迭代器了,返回一个对象,且里面有next方法,next返回value和done如果为最后一个则done为true

1、自定义迭代器

const obj={a:1,b:2}//首先定一个对象
function myIterator(){  //实现迭代器
    let self=this
    let index=0; //使用闭包的方式来记录下标
    const keys=Object.keys(self);
    return {
        next(){   //next方法返回value和done
           if(index<keys.length){
               return {value:self[keys[index++]],done:false}
           }return {value:undefined,done:true}
        }
    }
}
//定义到Symbol.iterator方法上
obj[Symbol.iterator]=myIterator;

for(const i of obj){
    console.log(i)
}

上面的方法可以看到我们的对象通过自定义迭代器的方法就可以使用for...of..来进行遍历啦。

2、通过使用生成器来定义产生迭代器
上面的方法我们是使用了自定义函数的方法来生成迭代器,但是js其实提供了一个方式来让我们更优雅的生成的一个迭代器,那就是生成器(generater)函数,来看定义:

image.png 首先我们根据字面意思就知道生成器就是一个函数,而且这个函数呢,是以function* 开头的,执行了生成器函数,就会返回了一个迭代器,那迭代器的next方法哪里来呢,里面不是还要返回value和done吗,mdn上对生成器函数有这样一句话:

生成器函数提供了一个强大的选择:它允许你定义一个包含自有迭代算法的函数, 同时它可以自动维护自己的状态。最初调用时,生成器函数不执行任何代码,而是返回一种称为Generator的迭代器。 通过调用生成器的下一个方法消耗值时,Generator函数将执行,直到遇到yield关键字。 这时候我们另一个关键字就出来了yield,我们可以看到,生成器函数是会自己维护状态,像上面我们自定义的方式我们通过设置index闭包的形式来维护next函数就是我们的状态,下面我们看个例子就可以知道生成器函数的作用啦。

function* myIterator(){
    yield 1;
    yield 2;
}
const i=myIterator();
i.next();//{value:1,done:false}
i.next();//{value:2,done:true}

可以看到我们定一个生成器函数执行他他返回了一个迭代器,这个迭代器就自动有next方法,value就是yield后面的值,这就是说明函数内部维护了我们的状态,不需要我们手动去维护,每次执行了next方法后,就会停留在下一个yield方法,我们可以更改上面的方法使用生成器来实现@@iterator

const obj={a:1,b:2}
function* myIterator(){
    const self=this;
    for(const key of Object.keys(self)){
        yield [keys,self[key]];
    }
}
obj[Symbol.iterator]=myIterator
for(const a of obj){
    console.log(a);
} 
// 'a' 1
// 'b' 2