美文网首页
【ES6 笔记】迭代器和生成器

【ES6 笔记】迭代器和生成器

作者: ___Jing___ | 来源:发表于2018-11-06 16:57 被阅读0次

    迭代器

    概念:迭代器是一种特殊对象,包含一个next()方法,每次调用都返回一个结果对象。结果对象有两个属性:一个是value,表示下一个将要返回的值;一个是done,布尔值,当没有更多可返回的数据时返回true。

    function createIterator(items){
        var i = 0 ;
        return {
            next : function (){
                var done = (i>=items.length);
                var value = !done?items[i++]:undefined;
                return {
                    done,
                    value
                }
            }
        }
    }
    
    var iterator = createIterator([1,2]) ; 
    
    console.log(iterator.next()); // {done: false, value: 1}
    console.log(iterator.next()); // {done: false, value: 2}
    //之后所有的调用都会返回相同的内容
    console.log(iterator.next());  // {done: true, value: undefined}
    

    上面只是原理,ES6中引入了生成器来简化创建迭代器的过程

    生成器

    概念:生成器是一种返回迭代器的函数,通过function关键字后的星号(*)来表示,函数中会用到关键字yield。星号可以紧挨着function关键字,也可以在中间添加一个空格:

    // 生成器
    function *createIterator(){
        yield 1;
        yield 2;
    }
    // 生成器的调用方式与普通函数相同,只不过返回的是一个迭代器
    var iterator = createIterator() ; 
    
    console.log(iterator.next());  //{value: 1, done: false}
    console.log(iterator.next());  //{value: 2, done: false}
    //之后所有的调用都会返回相同的内容
    console.log(iterator.next());  //{value: undefined, done: true}
    

    在这个例子中,星号表示这是一个生成器;yield关键字指定调用迭代器的next()的返回值和返回顺序。
    没当执行完一句yield语句后函数会自动停止执行。

    function *createIterator(items){
        for( let i=0; i<items.length; i++ ){
            yield items[i]
        }
    }
    
    let iterator = createIterator([1,2]) ; 
    
    console.log(iterator.next()); // {done: false, value: 1}
    console.log(iterator.next()); // {done: false, value: 2}
    //之后所有的调用都会返回相同的内容
    console.log(iterator.next());  // {done: true, value: undefined}
    

    yield关键字只可在生成器内部使用,嵌套的非生成器函数不算内部

    • 生成器函数表达式
    let createIterator = function *(items){
        for( let i=0; i<items.length; i++ ){
            yield items[i]
        }
    }
    
    let iterator = createIterator([1,2]) ; 
    
    console.log(iterator.next()); // {done: false, value: 1}
    console.log(iterator.next()); // {done: false, value: 2}
    //之后所有的调用都会返回相同的内容
    console.log(iterator.next());  // {done: true, value: undefined}
    

    不能用箭头函数来创建生成器

    • 生成器对象方法
      由于生成器本身就是函数,因而可以将它们添加到对象中。
    let obj = {
        createIterator : function *(items){
            for( let i=0; i<items.length; i++ ){
                yield items[i]
            }
        }
    }
    
    let iterator = obj.createIterator([1,2]) ; 
    

    简写法:

    let obj = {
        *createIterator(items){
            for( let i=0; i<items.length; i++ ){
                yield items[i]
            }
        }
    }
    
    let iterator = obj.createIterator([1,2]) ; 
    

    可迭代对象和for-of循环

    可迭代对象具有Symbol.iterator属性,通过制定的函数可以返回一个作用于附属对象的迭代器。
    由于生成器默认会为Symbol.iterator属性赋值,因此所有通过生成器创建的迭代器都是可迭代对象。
    for-of循环每执行一次都会调用可迭代对象的next()方法,并将迭代器返回的结果对象的value属性存储在一个变量中,循环将持续执行这一过程直到返回对象的done属性的值为true。

    let values = [ 1, 2, 3 ]
    
    for(let num of values){
        console.log(num)
    }
    // 分别输出1、2、3
    

    如果将for-of语句用于不可迭代对象、null或undefined将会导致程序抛出错误

    • 访问默认迭代器
      可以通过Symbol.iterator来访问对象默认的迭代器:
    let values = [ 1, 2, 3 ];
    let iterator = values[Symbol.iterator]();
    
    console.log(iterator.next()); // { value : 1, done : false }
    console.log(iterator.next()); // { value : 2, done : false }
    console.log(iterator.next()); // { value : 3, done : false }
    console.log(iterator.next()); // { value : undefined, done : true }
    

    由于具有Symbol.iterator属性的对象都有默认的迭代器,因此可以用它来检测对象是否为可迭代对象:

    function isIterable(object){
          return typeof object[Symbol.iterator] === 'function';
    }
    
    console.log( isIterable([1,2,3]) )  // true
    console.log( isIterable("Hello") )  // true
    console.log( isIterable(new WeakMap()) )  // false
    
    • 创建可迭代对象
      默认情况下,开发者定义的对象都是不可迭代对象,但如果给Symbol.iterator属性添加一个生成器,则可以将其变为可迭代对象:
    let collection = {
          items : [],
          *[Symbol.iterator](){
                for(let item of this.items){
                      yield item
                }  
           }
    }
    
    collection.items.push(1);
    collection.items.push(2);
    
    for (let x of collection){
        console.log(x)
    }
    // 分别输出1、2
    

    内建迭代器

    • 集合对象迭代器
      在ES6中有3种类型的集合对象:数组、Map集合和Set集合。
      这3种对象都内建了以下三种迭代器:

      • entries() 返回一个迭代器,其值为多个键值对;
      • values() 返回一个迭代器,其值为集合的值;
      • keys() 返回一个迭代器,其值为集合中的所有键名。

      entries()
      每次调用next()方法时,entries()迭代器都会返回一个数组,数组中的两个元素分别表示集合中每个元素的键与值。
      如果被遍历的对象是数组,则第一个元素是数字类型的索引;
      如果是Set集合,则第一个元素与第二个元素都是值(Set集合中的值被同时作为键与值使用);
      如果是Map集合,则第一个元素为键名,第二个元素为键值。

    let colors = ['red'];
    let numbers = new Set([1234]);
    let maps = new Map([['key','value']]);
    
    for (let entry of colors.entries()){
        console.log(entry); // [0, "red"]
    }
    for (let entry of numbers.entries()){
        console.log(entry); //[1234, 1234]
    }
    for (let entry of maps.entries()){
        console.log(entry); //["key", "value"]
    }
    

    values()
    调用values()迭代器时返回集合中所存在的值:

    let colors = ['red'];
    let numbers = new Set([1234]);
    let maps = new Map([['key','value']]);
    
    for (let entry of colors.values()){
        console.log(entry); //"red"
    }
    for (let entry of numbers.values()){
        console.log(entry); //1234
    }
    for (let entry of maps.values()){
        console.log(entry); //value
    }
    

    keys()
    调用keys()迭代器时返回集合中存在的每一个键:

    let colors = ['red'];
    let numbers = new Set([1234]);
    let maps = new Map([['key','value']]);
    
    for (let entry of colors.keys()){
        console.log(entry); // 0 
    }
    for (let entry of numbers.keys()){
        console.log(entry); // 1234
    }
    for (let entry of maps.keys()){
        console.log(entry); // ‘key’
    }
    

    不同集合类型的默认迭代器
    每个集合类型都有一个默认迭代器,在for-of循环中,如果没有显式指定则使用默认的迭代器。
    数组和Set集合的默认迭代器是values()方法
    Map集合的默认迭代器是entries()方法

    let colors = ['red'];
    let numbers = new Set([1234]);
    let maps = new Map([['key','value']]);
    
    for (let entry of colors){
        console.log(entry); // red
    }
    for (let entry of numbers){
        console.log(entry); // 1234
    }
    for (let entry of maps){
        console.log(entry); // ["key", "value"]
    }
    

    结构与for-of循环
    如果要在for-of循环中使用结构语法,则可以利用Map集合默认构造函数的行为来简化编码过程:

    let data = new Map();
    data.set('name','欧阳不乖')
    data.set('age',18)
    
    for(let [key, value] of data){
        console.log(`${key} = ${value}`)
    }
    // name = 欧阳不乖
    // age = 18
    
    • 字符串迭代器
    let message = 'Hi 欧阳不乖';
    for(let w of message){
      console.log(w)
    }
    //H
    // i
    // (空)
    //欧
    //阳
    //不
    //乖
    
    • NodeList迭代器
    let divs = document.getElementByTags('div');
    for (let div of divs){
        console.log(div.id)
    }
    

    展开运算符与非数组可迭代对象

    在数组字面量中可以多次使用展开运算符,将可迭代对象中的多个元素依次插入新数组中,替换原先展开运算符所在的位置:

    let smallNumbers = [ 1, 2, 3 ];
    let bigNumbers = [ 100, 101, 102 ];
    let allNumbers = [0, ...smallNumbers, ...bigNumbers];
    
    console.log(allNumbers); // [0, 1, 2, 3, 100, 101, 102]
    

    高级迭代器功能

    • 给迭代器传递参数
    function *createIterator(){
        let first = yield 1;
        let second = yield first +2;
        yield second +3;
    }
    let iterator = createIterator();
    
    console.log( iterator.next() );  // {value: 1, done: false}
    console.log( iterator.next(4) );  // {value: 6, done: false}
    console.log( iterator.next(5) );  // {value: 8, done: false}
    console.log( iterator.next() );  // {value: undefined, done: true}
    

    这里有一个特例,第一次调用next()方法时无论传入什么样的参数都会被丢弃。
    让我们从下面的代码分解步骤,来理解一下代码内部的具体细节:


    执行步骤
    • 在迭代器中抛出错误
      通过throw()方法,当迭代器恢复执行时可令其抛出一个错误。
    function *createIterator(){
        let first = yield 1;
        let second = yield first +2;
        yield second +3;
    }
    let iterator = createIterator();
    
    console.log( iterator.next() );  // {value: 1, done: false}
    console.log( iterator.next(4) );  // {value: 6, done: false}
    console.log( iterator.throw(new Error('BOOM')) );  //浏览器抛出错误:Uncaught Error: BOOM
    console.log( iterator.next() );  //这里什么都不会再发生了
    

    如下图所示,错误抛出则立即阻止了后续代码的执行,红色背景区域的代码永远都不会被执行了:


    错误抛出位置示意图
    • 生成器返回语句
      在生成器中,return表示所有操作已经完成,属性done被设置为true;如果同时提供了相应的值,则属性value会被设置为这个值:
    function *createIterator(){
        yield 1;
        return 'finished';  //之后的代码将不再执行
        yield 2;
        yield 3;
    }
    let iterator = createIterator();
    
    console.log(iterator.next()); // {value: 1, done: false}
    console.log(iterator.next()); // {value: "finished", done: true} 
    console.log(iterator.next()); // {value: undefined, done: true}
    

    通过return语句指定的返回值,只会在返回对象中出现一次,在后续调用返回的对象中,value属性会被重置为undefined。
    展开运算符与for-of循环语句会直接忽略通过return语句指定的任何返回值,只要done一变为true就立即停止读取其他的值。

    • 委托生成器
      在某些情况下,我们需要将两个迭代器合二为一,这时可以创建一个生成器,再给yield语句添加一个星号,就可以将生成数据的过程委托给其他迭代器。
      当定义这些生成器时,只需将星号放置在关键字yield和生成器的函数名之间即可:
    function *createNumberIterator(){
        yield 1;
        yield 2
    }
    function * createColorIterator(){
        yield 'red';
        yield 'blue';
    }
    function *createCombinedIterator(){
        yield *createNumberIterator();
        yield *createColorIterator();
        yield true;
    }
    let iterator = createCombinedIterator();
    
    console.log( iterator.next() );  // {value: 1, done: false}
    console.log( iterator.next() );  // {value: 2, done: false} 
    console.log( iterator.next() );  // {value: "red", done: false}
    console.log( iterator.next() );  // {value: "blue", done: false}
    console.log( iterator.next() );  // {value: undefined, done: true}
    

    yield *也可以直接应用于字符串,例如 yield *‘hello’

    异步任务执行

    • 简单任务执行器
    function run(taskDef){
        // 创建一个无使用限制的迭代器
        let task = taskDef();
        // 开始执行任务
        let result = task.next();
        // 循环调用next()的函数
        function step(){
            // 如果任务尚未完成,则继续执行
            if(!result.done){
                  result = task.next();
            }
        }
        // 开始迭代执行
        step();
    }
    
    run(function *(){
        console.log(1);
        yield;
        console.log(2)
    })
    // 1
    // 2
    
    • 向任务执行器传递数据
      给任务执行器传递数据最简单的办法是,把yield返回值传入下一次next()方法的调用:
    function run(taskDef){
        // 创建一个无使用限制的迭代器
        let task = taskDef();
        // 开始执行任务
        let result = task.next();
        // 循环调用next()的函数
        function step(){
            // 如果任务尚未完成,则继续执行
            if(!result.done){
                result = task.next(result.value);
                step();
            }
        }
        // 开始执行迭代
        step();
    }
    
    run(function *(){
        let value = yield 1;
        console.log(value);  // 1
    
        value = yield  value+3;
        console.log(value)  // 4
    })
    
    • 异步任务执行器

    相关文章

      网友评论

          本文标题:【ES6 笔记】迭代器和生成器

          本文链接:https://www.haomeiwen.com/subject/sdhuxqtx.html