美文网首页
39.迭代器和可迭代器对象

39.迭代器和可迭代器对象

作者: 静昕妈妈芦培培 | 来源:发表于2021-11-24 09:26 被阅读0次

    什么是迭代器

    • 迭代器是确使用户可在容器对象(container,例如链表或数组)上遍访的对象,使用该接口无需关心对象的内部实现细节
    • 迭代器是帮助我们对某个数据结构进行遍历的对象。
    • 在JavaScript中,迭代器也是一个具体的对象,这个对象需要符合迭代器协议(iterator protocol)
      • 迭代器协议定义了产生一系列值(无论是有限还是无限个)的标准方式;
      • 那么在js中这个标准就是一个特定的next方法
    • next方法有如下要求:
      • 一个无参函数有一个参数的函数,返回一个应当拥有以下两个属性的对象
      • done(boolean)
        • 如果迭代器可以产生序列中的下一个值,则为false.
        • 如果迭代器已将序列迭代完毕,则为true.这种情况下,value是可选的,如果它依然存在,即为迭代结束后的默认返回值。
      • value
        • 迭代器返回的任何JavaScript值。done为true时,value可省略。

    1.编写一个迭代器:

    迭代器是一个对象
    迭代器实现了特定的next方法

    • next方法是一个无参函数或有一个参数的函数
    • 返回值为一个对象,对象有done和value两个属性
    const iterator = {
      next() {
        return {
          done: false,
          value: "aaa",
        };
      },
    };
    
    // 调用迭代器的next方法,返回一个对象
    console.log(iterator.next()); // { done: false, value: 'aaa' }
    

    迭代器可以帮助我们对某个数据结构进行遍访
    而上面并没有实现遍访某个数据结构
    我们需要实现,在调用迭代器的next方法时,可以依次访问某个数据结构中的元素

    const names = ["lily", "koby", "curry", "coderwhy"];
    let index = 0;
    const namesIterator = {
      next() {
        if (index < names.length) {
          return { done: false, value: names[index++] };
        } else {
          return { done: true }; // done为true时,value可省略
          // return {done: true, value: undefined} 返回对象的value可以为undefined
          // return {done: true, value: '默认值'} 返回对象的value可以为任意默认值
        }
      },
    };
    
    console.log(namesIterator.next()); // { done: false, value: 'lily' }
    console.log(namesIterator.next()); // { done: false, value: 'koby' }
    console.log(namesIterator.next()); // { done: false, value: 'curry' }
    console.log(namesIterator.next()); // { done: false, value: 'coderwhy' }
    console.log(namesIterator.next()); // { done: true }
    console.log(namesIterator.next()); // { done: true }
    
    
    

    2.封装一个可以生成迭代器的函数:

    /**
     * 封装一个可为数组产生迭代器的函数
     * 返回一个迭代器对象
     */
    function createArrayIterator(arr) {
      let index = 0;
      return {
        next() {
          if (index < arr.length) {
            return { done: false, value: arr[index++] };
          } else {
            return { done: true, value: undefined };
          }
        },
      };
    }
    
    const names = ["lily", "koby"];
    const namesIterator = createArrayIterator(names);
    console.log(namesIterator.next()); // { done: false, value: 'lily' }
    console.log(namesIterator.next()); // { done: false, value: 'koby' }
    console.log(namesIterator.next()); // { done: true, value: undefined }
    
    const nums = [1, 3, 4];
    const numsIterator = createArrayIterator(nums);
    console.log(numsIterator.next()); // { done: false, value: 1 }
    console.log(numsIterator.next()); // { done: false, value: 3 }
    console.log(numsIterator.next()); // { done: false, value: 4 }
    console.log(numsIterator.next()); // { done: true, value: undefined }
    
    
    /**
     * 创建一个无限的迭代器
     */
    function createNumbersIterator() {
      let index = 0;
      return {
        next() {
          return { done: false, value: index++ };
        },
      };
    }
    const numbersIterator = createNumbersIterator();
    console.log(numbersIterator.next()); // { done: false, value: 0 }
    console.log(numbersIterator.next()); // { done: false, value: 1 }
    console.log(numbersIterator.next()); // { done: false, value: 2 }
    console.log(numbersIterator.next()); // { done: false, value: 3 }
    
    

    2.可迭代对象

    当一个对象实现了iterable protocol协议时,它就是一个可迭代对象
    这个对象要求必须实现@@iterator方法,在代码中我们使用Symbol.iterator访问该属性
    @@iterator方法执行必须返回一个迭代器

    /**
     * 可迭代对象:
     *    是一个对象
     *    有一个Symbol.iterator属性,属性值为一个函数,执行函数会返回一个迭代器
     */
    
    // 创建一个可迭代对象
    const iterableObj = {
      values: ["koby", "lily"],
      [Symbol.iterator]: function () {
        let index = 0;
        return {
          next: () => {
            if (index < this.values.length) {
              return { done: false, value: this.values[index++] };
            } else {
              return { done: true, value: undefined };
            }
          },
        };
      },
    };
    
    // 调用可迭代对象的Symbol.iterator方法可获取生成器
    const iterator = iterableObj[Symbol.iterator]();
    
    //执行生成器的next方法,可以遍访某个数据结构
    console.log(iterator.next()); // { done: false, value: 'koby' }
    console.log(iterator.next()); // { done: false, value: 'lily' }
    console.log(iterator.next()); // { done: true, value: undefined }
    
    

    for of可以遍历的东西必须必须是一个可迭代对象

    • 1.它会通过调用对象的Symbol.iterator方法获取迭代器iterator
    • 2.通过执行迭代器的next方法去访问目标容器中的元素,取到next方法返回的对象的value值,赋值给item
    • 3.当next方法返回对象的done值为true,结束遍历目标

    下面示例,for of遍历了一个不可迭代对象,结果报错

    const obj = {
      name: "why",
      age: 18,
    };
    
    //for of 遍历不同对象会报错,因为普通对象不是一个可迭代对象:TypeError: obj is not iterable
    for (const item of obj) {
      console.log(item);
    }
    
    

    下面示例,for of遍历了一个可迭代对象

    const iterableObj = {
      values: ["koby", "lily"],
      [Symbol.iterator]: function () {
        let index = 0;
        return {
          next: () => {
            if (index < this.values.length) {
              return { done: false, value: this.values[index++] };
            } else {
              return { done: true, value: undefined };
            }
          },
        };
      },
    };
    
    for (const item of iterableObj) {
      console.log(item)
    }
    

    输出:

    koby
    lily
    

    原生可迭代对象

    事实上,我们平时创建的很多原生对象已经实现了可迭代协议,本身就是一个可迭代对象:

    • String、Array、Map、Set、arguments对象,NodeList对象
    /**
     * String、Array、Map、Set、arguments对象,NodeList对象本身就是可迭代对象
     */
    
    // 字符串对象是可迭代对象
    const str = "abc";
    console.log(str[Symbol.iterator]); // [Function: [Symbol.iterator]]
    for (const s of str) {
      console.log(s);
    }
    // a
    // b
    // c
    
    // 数组对象是可迭代对象
    const arr = ["lily", "koby"];
    console.log(arr[Symbol.iterator]); // [Function: values]
    for (const a of arr) {
      console.log(a);
    }
    // lily
    // koby
    
    // Set对象是可迭代对象
    const set = new Set();
    console.log(set[Symbol.iterator]); // [Function: values]
    set.add(1);
    set.add(2);
    for (const item of set) {
      console.log(item);
    }
    // 1
    // 2
    
    // Map对象是可迭代对象
    const map = new Map();
    console.log(map[Symbol.iterator]); // [Function: entries]
    map.set(0, "你");
    map.set(1, "好");
    for (const m of map) {
      console.log(m);
    }
    // [ 0, '你' ]
    // [ 1, '好' ]
    
    // arguments是可迭代对象
    function sum(a, b) {
      console.log(arguments[Symbol.iterator]); // [Function: values]
    
      for (const a of arguments) {
        console.log(a);
      }
      return a + b;
    }
    
    sum(5, 6);
    
    // 5
    // 6
    
    
    

    可迭代对象的用法

    可迭代对象可用在:

    • JavaScript语法:for...of、展开语法、yield*、解构赋值;
    • 创建一些对象时:new Map([iterable])、new WeakMap([iterable])、new Set([iterable])、new WeakSet([iterable]);
    • 一些方法的调用:Promise.all(iterable)、Promise.all(iterable)、Array.from(iterable)
    /**
     * 可迭代对象用法:
     *  for of
     *  展开运算符...
     *  解构
     *  创建一些其他对象:作为new Set()的参数,Array.from()的参数
     */
    
    const iterableObj = {
      values: ["lily", "koby"],
      [Symbol.iterator]: function () {
        let index = 0;
        return {
          next: () => {
            if (index < this.values.length) {
              return { done: false, value: this.values[index++] };
            } else {
              return { done: true };
            }
          },
        };
      },
    };
    
    // 展开运算符
    const arr = ["abc", "cde"];
    const newArr = [...arr, ...iterableObj];
    console.log(newArr); //[ 'abc', 'cde', 'lily', 'koby' ]
    
    // 对象虽然不是可迭代对象,但依然可以使用展开运算符展开,
    // 这是ES9(ES2018)新增的特性,内部对对象做了特殊处理,用的不是迭代器
    const obj = { name: "why", age: 18 };
    const newObj = { ...obj };
    console.log(newObj); //{ name: 'why', age: 18 }
    
    // 解构
    const [str1, str2] = arr;
    console.log(str1, str2); //abc cde
    //对象的解构依然是ES9新增语法,内部对对象做了特殊处理,用的不是迭代器
    const { name, age } = obj;
    console.log(name, age); // why 18
    
    // 创建一些其他对象:作为new Set()的参数,Array.from()的参数
    
    //1.把数组转换为Set对象
    const set1 = new Set(iterableObj);
    const set2 = new Set(arr);
    
    console.log(set1); // Set(2) { 'lily', 'koby' }
    console.log(set2); // Set(2) { 'abc', 'cde' }
    
    //2.把类数组arguments转换为数组
    function sum(a, b) {
      const args = Array.from(arguments);
      console.log(args); // [ 1, 2 ]
      return a + b;
    }
    
    sum(1, 2);
    
    // Promise.all和Promise.race的参数为一个可迭代对象
    Promise.all(iterableObj).then((res) => {
      console.log(res); // [ 'lily', 'koby' ]
    });
    
    

    自定义类的可迭代性

    案例:创建一个Classroom类

    • 教室中有自己的位置,名称,当前教室的学生
    • 这个教室可以进来新学生(add)
    • 创建的教室对象都是可迭代对象
    /**
     * 案例:创建一个Classroom类
     * 教室中有自己的位置,名称,当前教室的学生
     * 这个教室可以进来新学生(add)
     * 创建的教室对象都是可迭代对象
     */
    class Classroom {
      constructor(name, address, students) {
        this.name = name;
        this.address = address;
        this.students = students;
      }
    
      add(stu) {
        this.students.push(stu);
      }
    
      [Symbol.iterator] = function () {
        let index = 0;
        return {
          next: () => {
            if (index < this.students.length) {
              return { done: false, value: this.students[index++] };
            } else {
              return { done: true };
            }
          },
          // 监听迭代器被提前终止,正常终止监听不到
          return: () => {
            console.log("监听到迭代器终止了");
            return { done: true };
          },
        };
      };
    }
    
    const classroom = new Classroom("测控技术与仪器", "226", [
      "lily",
      "koby",
      "curry",
    ]);
    for (const stu of classroom) {
      console.log(stu);
      if (stu === "koby") break;
    }
    
    // lily
    // koby
    // 监听到迭代器终止了
    
    

    **迭代器在某些情况下会在没有完全迭代的情况下中断:

    • 比如遍历的过程中通过break、continue、return、throw中断了循环操作
    • 比如在解构的时候,没有完全解构
      那么这个时候我们想要监听中断的话,可以添加return方法

    非常感谢王红元老师的深入JavaScript高级语法让我学习到很多 JavaScript 的知识

    相关文章

      网友评论

          本文标题:39.迭代器和可迭代器对象

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