ES6的迭代器、生成器到底是什么?

3,091 阅读4分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第3天,点击查看活动详情

前言

大家好,我是侃如,最近在复习ES6,打算扫清一些自己之前学习忽略的知识点。看了一下,不知不觉都到ES13了(类加了#关键字、await也可以顶层调用了......) 新特性暂且不提(有机会后面总结下)。跟身边的小伙伴沟通了下,发现部分同学对迭代器、生成器还比较模糊,怎么用?为什么用?完全是一头雾水,借这个机会跟大家分享下自己的复习成果,希望也能帮大家扫除一些知识盲点。

不要在面试官问你时,才想起来被自己忽略的知识点。

迭代器

Iterator被称为迭代器,在JavaScript中,迭代器是一个函数对象,它存在于可迭代对象的原型链中,可以通过可迭代对象的生成方法Symbol.iterator来访问。

image.png

迭代器可以通过使用next() 方法实现迭代,next()方法会返回具有两个属性的对象:value和 done,value表示当前迭代的值,done是一个布尔值表示迭代是否结束。如果迭代结束,则done返回true否则返回false。

        const arr = ["a","b","c"];
        //Array Iterator {}
        console.log(arr[Symbol.iterator]());
        const it = arr[Symbol.iterator]();
        //{value: "a", done: false}
        console.log(it.next());
        //{value: "b", done: false}
        console.log(it.next());
        //{value: "c", done: false}
        console.log(it.next());
        //{value: undefined, done: true}
        console.log(it.next());

迭代协议

迭代协议分为两个:可迭代协议、迭代器协议。

可迭代协议:定义迭代行为的协议,一个对象本身或原型链上存在一个可以通过Symbol.iterator调用的iterator属性,那么这个对象就是可迭代对象。

迭代器协议:定义了迭代器的迭代方式,调用next()方法,返回对应结构。

内置可迭代对象

自身或原型链上存在Symbol.iterator方法的对象被称为可迭代对象,自身或原型链本就内置Symbol.iterator方法的对象被称为内置可迭代对象。 常见的内置可迭代对象:

  • Array
  • string
  • Set
  • Map
  • 类数组对象

这块不清楚的可以阅读往期文章: Set、Map、类数组,傻傻区分不清楚?

生成器

生成器允许我们创建一个可以自动维护自己的状态的迭代函数,它是可迭代对象也是迭代器。

生成器函数的特点:

生成器通过 function* 创建,和普通函数相比它多了一个 * ,调用生成器函数时它不会执行任何代码,仅会返回一个叫 Generator 的迭代器,可以按迭代器协议进行迭代。

        function* fun() {
           console.log(1);
        }
        fun();//不执行内部打印
        
        //1
        //{value: undefined, done: true}
        console.log(fun().next());

生成器函数多次调用该函数,每次都会返回一个新的 Generator,每个 Generator 仅可以迭代一次。

yield 表达式

生成器函数中可以书写yield 表达式用来定义函数的内部状态, Generator会指向这个状态。

        function* fun() {
            console.log(1)
            yield 'a'
            console.log(2)
            yield 'b'
            console.log(3)
            yield 'c'
        }
        const g = fun()
        //1
        // {value: "a", done: false}
        console.log(g.next());
        //2
        // {value: "b", done: false}
        console.log(g.next());
        //3
        // {value: "c", done: false}
        console.log(g.next());
   
        // {value: undefined, done: true}
        console.log(g.next());

next传参

next 方法可以传入参数,传入的参数会作为上一步yield的返回值。

        function* fun() {
            console.log("start")
            const a = yield 'a'
            console.log(a)
            const b = yield 'b'
            console.log(b)
            const c = yield 'c'
            console.log(c)
        }
        const g = fun()
        //start
        // {value: "a", done: false}
        console.log(g.next("d"));
        
        //e
        // {value: "b", done: false}
        console.log(g.next("e"));
        
        //f
        // {value: "c", done: false}
        console.log(g.next("f"));
        
        //g
        // {value: undefined, done: true}
        console.log(g.next("g"));

看一下上面这段代码,可能不是那么好理解,可以多捋捋。

第一次给next()传的参数是"d",但是上一步没有yield,也没有打印,打印start,value为"a",done为false;

第二次给next()传的参数是"e",上一步存在yield,打印"e",value为"b",done为false;

第三次给next()传的参数是"f",上一步存在yield,打印"f",value为"c",done为false;

第四次给next()传的参数是"g",上一步存在yield,打印"g",此时不存在yield,value为undefined,done为true,结束迭代;

return方法

当生成器函数使用return方法时,会返回其携带的参数,同时结束Generator。

         function* fun() {
            console.log(1)
            yield 'a'
            console.log(2)
            yield 'b'
            console.log(3)
            yield 'c'
        }
        const g = fun()
        //1
        // {value: "a", done: false}
        console.log(g.next());
        
        // {value: "return", done: true}
        console.log(g.return("return"));
     
        // {value: undefined, done: true}
        console.log(g.next());
   
        // {value: undefined, done: true}
        console.log(g.next());

yield* 表达式

yield* 会返回一个迭代器对象,相当于在Generator内部再调用另一个Generator。

         function* fun() {
            yield 'a';
            yield 'b';
            yield 'c';
        }
        function* fun1() {
            yield* fun();
            
        }
        const g = fun1()
        
        // {value: "a", done: false}
        console.log(g.next());
        
        // {value: "b", done: false}
        console.log(g.next());
     
        // {value: "c", done: false}
        console.log(g.next());
   
        // {value: undefined, done: true}
        console.log(g.next());

为什么要使用迭代器、生成器?

大家都知道在JavaScript中,遍历的方法很多:遍历数组的for循环、遍历Set、Map的forEach、遍历对象的for...in等。那为什么要再加一个迭代器呢? 迭代器是一个统一的遍历方法,无论是数组、字符串还是Set、Map只要是可迭代对象都可以用它遍历。

当然,next()方法过于繁琐,需要频繁调用,我们通常会使用for...of来进行遍历,可以使用for...of的必定是可迭代对象。

使用生成器函数可以将一个不可迭代的对象变成一个可迭代对象

        const obj = {};

        obj[Symbol.iterator] = function* () {
            yield "a";
            yield "b";
            yield "c";
        };
        for (const value of obj) {
            
            //a
            //b
            //c
            console.log(value);
        }