最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • JavaScript之迭代器和生成器

    正文概述 掘金(Hzzy)   2021-04-05   557

    迭代器(遍历器)

    什么是迭代器?

    首先迭代器是一个对象(它很特殊),这个对象可以遍历类数组的数据结构

    其次这个对象里有两个方法

    • next() //遍历数据结构里的成员
    • return () //终止遍历

    为什么要迭代器呢?

    可以提高开发效率!开发者不需要知道数据结构的结构,就能按一定的顺序拿到数据结构里的数据。是不是很高级?

    那要怎么要生成一个迭代器呢?

    要知道怎么生成一个迭代器,先要知道什么是可迭代协议

    大白话就说是,创建一个可迭代的对象时,这个可迭代对象必须要自己定义一个迭代器工厂函数,这样就可以利用自己的迭代器来遍历自己的数据了!

    很多内置类型都是可迭代对象,意思就是说它们都有自己内置的迭代器!,比如

    • 字符串
    • 数组
    • 映射
    • 集合
    • arguments对象
    • NodeList等DOM集合类型

    现在就来玩一玩迭代器!

    let array=["abc","hello","hy","world"];
    let arrayIterator=array[Symbol.iterator]();
    /*
    调用数组的迭代器工厂函数,生产出一个数组迭代器
    */
    console.log(arrayIterator.next());//{ value: 'abc', done: false }
    console.log(arrayIterator.next());//{ value: 'hello', done: false }
    console.log(arrayIterator.next());//{ value: 'hy', done: false }
    console.log(arrayIterator.next());//{ value: 'world', done: false }
    console.log(arrayIterator.next());//{ value: undefined, done: true }
    

    是不是很神奇呢!,上面代码还可以继续优化一下!

    let array=["abc","hello","hy","world"];
    let arrayIterator=array[Symbol.iterator]();
    
    for(value of  array){
        console.log(value);
    }
    /*
    abc
    hello
    hy
    world
    */
    console.log("------------------------------")
    for(value   of  arrayIterator){
        console.log(value);
    }
    /*
    内置类型的迭代器本身也是一个可迭代对象,所以也能被for....of消费
    abc
    hello
    hy
    world
    */
    

    讲一下为什么上面可以那样写

    因为有一些原生语言特性实现了Iterator接口,只要实现了Iterator(迭代器)接口就能消费Iterable(可迭代对象),上面的for...of它本质上是一个迭代器,所以它可以消费数组(可迭代对象),其实不只for...of,还有以下的原生语言特性实现了Iterator接口

    • 数组解构
    • 扩展操作符
    • Array.from()
    • 创建集合
    • 创建映射
    • Promise.all()接收期约组成的可迭代对象
    • Promise.race()接收期约组成的可迭代对象
    • yield*操作符,在生成器中使用

    提前终止迭代器

    使用迭代器对象的return()方法,可以停止迭代器遍历,但是要注意的是像数组这样的内置对象,即使你执行了return()方法,它也不会让迭代器关闭,而且它还会保留之前遍历的现场,等下次继续开遍历时,会接着上次遍历的地方,继续遍历下去。

    下面上代码

    let array=[1,2,3,4,5];
    
    let arrayIterator=array[Symbol.iterator]();
    arrayIterator.return=function(){
        console.log("停止遍历");
        return {done:true};
    }
    
    for(value  of  arrayIterator){
        if(value >2){
            break;
        }
        console.log(value);
    }
    /*
    1
    2
    停止遍历
    */
    console.log(arrayIterator.next());//{ value: 4, done: false }
    console.log(arrayIterator.next());//{ value: 5, done: false }
    console.log(arrayIterator.next());//{ value: undefined, done: true }
    

    自定义一个迭代器

    看完上面内置的迭代器后,自己是不是想实现一个迭代器呢^^

    现在来说一下,实现一个自定义的迭代器要注意什么!

    在迭代器工厂函数里要定义1个变量

    • 变量index:保存迭代器指针的位置,这样下次执行遍历的时候,就会接着这个位置,遍历下一个数据(利用闭包,保存遍历现场)
    let obj={
        data:[0,1,3],
        [Symbol.iterator](){
            let index=0;//保存指针的位置
            const self=this;
            return {
                next(){
                    if(index<self.data.length)
                    {
                        return {value:self.data[index++],done:false};
                    }
                    else
                        return {value:"undefined",done:true}
                }
            }
        }
    }
    
    let objIterator =obj[Symbol.iterator]();
    console.log(objIterator.next());//{ value: 0, done: false }
    console.log(objIterator.next());//{ value: 1, done: false }
    console.log(objIterator.next());//{ value: 3, done: false }
    console.log(objIterator.next());//{ value: 'undefined', done: true }
    

    JavaScript之迭代器和生成器

    看完图是不是清晰了很多^^

    生成器

    Generator(生成器) 函数是 ES6 提供的一种异步编程解决方案 ,它是一个状态机,还是一个迭代器生成函数。

    调用一个Generator函数,会返回一个迭代器,该迭代器调用next()函数,可以遍历Generator函数中的状态。

    Generator函数里,会出现yield关键字,每一个yield关键字就代表Generator函数的一个状态,只要迭代器遍历到yield关键字,生成器函数(Generator)就会中断执行,那什么时候它会再一次执行呢?等到迭代器再次调用next()方法时,生成器函数又会开始执行。

    那下面就声明一个简单的Generator函数吧!

    function  * createGenerator(){
        console.log("start");
        yield  1;
        yield  2;
        yield  3;
        return "ending";
    }
    
    
    let Generator=createGenerator();//生成了一个迭代器,虽然调用了生成器函数,但是它里面的代码是不会执行的,只有迭代器的next()方法才能驱动生成器函数执行内部的代码。
    console.log(Generator.next());
    /*
    Generator 函数开始执行,直到遇到第一个yield表达式为止。next方法返回一个对象(IteratorResult),它的value属性就是当前yield表达式的值1,done属性的值false,表示遍历还没有结束。
    所以在"yield 1"语句前面的"console.log("start")"会执行。
    */
    console.log(Generator.next());//{done:false,value:2}
    console.log(Generator.next());//{done:false,value:3}
    console.log(Generator.next());//{done:true,value:"ending"}
    console.log(Generator.next());//{done:true,value:undefined}
    console.log(Generator.next());//{done:tre,value:undefined}
    

    生成器对象可以作为可迭代对象

    废话不多说,直接上代码

    function *generator(){
        yield   1;
        yield   2;
        yield   3;
        yield   4;
    }
    
    let generatorIterator=generator();//生成器函数返回一个迭代器(生成器对象)
    let generatorIteratorIterator=generatorIterator[Symbol.iterator]();
    /*
    生成器函数返回的迭代器,本质上也是一个可迭代对象,因为它有一个默认迭代器,所以可以用for...of结构来消费生成器对象	
    */
    console.log(generatorIteratorIterator)
    for(item of generatorIterator){
        console.log(item);
    }
    /*
    1
    2
    3
    4
    */
    //和下面的代码等价
    console.log(generatorIterator.next().value);//1
    console.log(generatorIterator.next().value);//2
    console.log(generatorIterator.next().value);//3
    console.log(generatorIterator.next().value);//4
    

    使用yield实现输入和输出

    从阮一峰大神上复制过来的一个例子,我觉得特别好理解

    先说明一下,除非下一个next()方法给上一个yield表达式注入参数,否则yield表达式总是返回undefined(这里可能稍微有点难理解,等下我会上图^^)

    function* foo(x) {
        var y = 2 * (yield (x + 1));
        var z = yield (y / 3);
        return (x + y + z);
    }
    
    var a = foo(5);
    console.log(a.next()); // Object{value:6, done:false}
    console.log(a.next()); // Object{value:NaN, done:false}
    console.log(a.next()); // Object{value:NaN, done:true}
    var b = foo(5);
    b.next() // { value:6, done:false }
    b.next(12) // { value:8, done:false }
    b.next(13) // { value:42, done:true }
    
    1. 声明了一个变量a,接收生成器foo返回的迭代器。
    2. 执行第一个a.next(),这时候第一个a.next()会输出一个IteratorResult对象包含了value(yield表达式的值)和done(遍历是否完成)两个属性,这里要特别注意的是!!!!变量y的赋值操作被中断了!!!
    3. 执行第二个a.next(),因为第二个a.next()方法没有向生成器函数注入实参,所以上一个yield表达式返回的是undifined,结果y=2*undefined,这就是为什么第二个a.next().value输出的值是NaN,之后变量z的赋值操作被中断。
    4. 执行第三个a.next(),因为第三个a.next()方法没有向生成器函数注入实参,所以上一个yield表达式返回的是undifined,结果z=undefined,这就是为什么第三个a.next().value输出的值是NaN

    大家自己来模拟一下吧!

    产生可迭代对象

    可以使用星号增强yield的行为,让它能够迭代一个可迭代对象,直接上代码

    function *Generator(){
        yield *[1,2,3]
    }
    
    for(item  of    Generator()){
        console.log(item);
    }
    //等价
    function *Generator(){
        yield   1;
        yield   2;
        yield   3;
    }
    
    for(item  of    Generator()){
        console.log(item)
    }
    

    生成器作为默认迭代器

    let obj={
        data:[1,2,3],
        [Symbol.iterator](){
            let index=0;
            let self=this;
            return {
                next(){
                    if(index<self.data.length){
                        return{
                            value:self.data[index++],
                            done:false
                        }
                    }
                    else
                        return{
                            value:undefined,
                            done:true
                        }
                }
            }
        }
    }
    
    let obj1= {
        data: [1, 2, 3],
        [Symbol.iterator]:function *Generator(){
            yield *this.data;
        }
    }
    
    for(item of obj){
        console.log(item);
    }
    /*
    1
    2
    3
    */
    
    for(item of obj1){
        console.log(item);
    }
    /*
    1
    2
    3
    */
    

    看完上面的代码是不是觉得让生成器作为默认迭代器很方便!

    提前终止生成器

    return方法

    使用生成器的return()方法可以提前终止生成器,关闭生成器以后,后续调用next()方法会显示done:true状态,而提供的任何返回值都不会被存储或传播,不废话了,直接上代码。

    function *generator(){
        yield  1;
        yield  2;
        yield  3;
        yield  4;
        yield  5;
        yield  6;
        yield  7;
        yield  8;
        yield  9;
    }
    
    let Iterator=generator();
    console.log(Iterator.next());//{ value: 1, done: false }
    console.log(Iterator.next());//{ value: 2, done: false }
    console.log(Iterator.return("生成器关闭"));//{ value: '生成器关闭', done: true }
    console.log(Iterator.next());//{ value: undefined, done: true }
    console.log(Iterator.next());//{ value: undefined, done: true }
    console.log(Iterator.next());//{ value: undefined, done: true }
    console.log(Iterator.next());//{ value: undefined, done: true }
    

    throw方法

    throw()方法会再暂停的时候将一个提供的错误注入到生成器对象中。如果错误未处理,生成器就会关闭,直接上代码。

    function *generator(){
        yield  1;
        yield  2;
        yield  3;
        yield  4;
        yield  5;
        yield  6;
        yield  7;
        yield  8;
        yield  9;
    }
    
    let Iterator=generator();
    try {
        console.log(Iterator.next());
        console.log(Iterator.next());
        console.log(Iterator.throw("生成器关闭"));
        console.log(Iterator.next());
        console.log(Iterator.next());
        console.log(Iterator.next());
        console.log(Iterator.next());
    }catch(e){
        console.log(e);
    }
    /*
    { value: 1, done: false }
    { value: 2, done: false }
    生成器关闭
    */
    
    ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
    function *generator(){
        try{
            yield  1;
            yield  2;
            yield  3;
            yield  4;
            yield  5;
            yield  6;
            yield  7;
            yield  8;
            yield  9;
        }catch(e){
            console.log(e);
        }
    }
    
    let Iterator=generator();
    try {
        console.log(Iterator.next());
        console.log(Iterator.next());
        console.log(Iterator.throw("生成器关闭"));
        console.log(Iterator.next());
        console.log(Iterator.next());
        console.log(Iterator.next());
        console.log(Iterator.next());
    }catch(e){
        console.log(e);
    }
    /*
    { value: 1, done: false }
    { value: 2, done: false }
    生成器关闭
    { value: undefined, done: true }
    { value: undefined, done: true }
    { value: undefined, done: true }
    { value: undefined, done: true }
    { value: undefined, done: true }
    */
    

    到这里生成器的内容就差不多了,下面来说一说生成器的一些应用场景

    应用场景

    协程场景

    什么是协程?

    我这里就不多说了,可以参考一下廖雪峰老师的文章,直接上代码

    function  cookTask(wash,cook){
        let washIterator=wash();
        let cookIterator=cook();
        console.log(washIterator.next().value);
        console.log(cookIterator.next().value);
        console.log(washIterator.next().value);
        console.log(cookIterator.next().value);
        console.log(washIterator.next().value);
        console.log(cookIterator.next().value);
    }
    function  *wash(){
        yield "我洗好了白菜";
        yield "我洗好了猪肉";
        yield "我洗好了鸡翅";
    }
    
    function  *cook(){
        yield  '我煮好了白菜';
        yield  '我煮好了猪肉';
        yield  '我煮好了鸡翅';
    }
    
    cookTask(wash,cook);
    /*
    我洗好了白菜
    我煮好了白菜
    我洗好了猪肉
    我煮好了猪肉
    我洗好了鸡翅
    我煮好了鸡翅
    */
    

    我们这里模拟了炒菜的场景,这里有一个机器人厨师,首先这个机器人会执行洗菜的功能,当洗菜功能完成了,就将控制权让出给煮菜的功能,当煮菜的功能结束了,就将控制权让出给洗菜的功能。

    异步操作同步化

    这是阮一峰大神的一个例子

    让ajax异步操作同步化的核心就是:Generator函数里的yield关键字,它可以中断执行!

    function* main() {
        var result = yield request("http://some.url");
        var resp = JSON.parse(result);
        console.log(resp.value);
    }
    
    function request(url) {
        makeAjaxCall(url, function(response){
            it.next(response);
        });
    }
    
    var it = main();
    it.next();
    

    说一下思路

    当迭代器it第一次调用了next()方法,启动生成器,ajax就会发起请求,当ajax完成时,通过回调函数,它就会再次启动next()方法,将接收到的数据,赋值给上一次yield表达式,于是变量result就收到了ajax发送过来的请求,最后把请求到的数据进行格式化并输出。

    用Generator函数把异步任务同步化,是不是很神奇^^

    结尾

    这篇文章到这里就结束了,以上如果有什么内容是错误的,希望大佬们能指出^^


    起源地下载网 » JavaScript之迭代器和生成器

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元