美文网首页让前端飞首页推荐
深入理解ES6 ---- 迭代器和生成器

深入理解ES6 ---- 迭代器和生成器

作者: 漓漾li | 来源:发表于2019-04-08 18:31 被阅读3次

    迭代器

    遍历器(Iterator)是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)。

    (1) Iterator 的作用
    • 为各种数据结构,提供一个统一的、简便的访问接口
    • 使得数据结构的成员能够按某种次序排列
    • ES6 创造了一种新的遍历命令for...of循环,Iterator 接口主要供for...of消费。
    (2) Iterator的迭代过程

    所有的迭代器对象都会有一个next方法,每次调用都会返回一个对象:{done: boolean, value: any}value表示当前成员的值,done表示是否还有更多的数据。迭代器内部会维护一个指针,指向当前成员的位置,每次调用next都会指向下一个成员。

    // es5实现迭代器
    function creatIterator(arr){
        let index = 0;
        return {
          next: () =>{
            return {
              done: index > arr.length - 1,
              value: this.done ? undefined : arr[index++]
            }
          }
        }
    }
    const iterator = creatIterator([1, 2, 3])
    console.log(a.next())  // { done: false, value: 1 }
    console.log(a.next())  // { done: false, value: 2 }
    console.log(a.next())  // { done: false, value: 3 }
    console.log(a.next())  // { done: true, value: undefined }
    // 之后的调用都会返回相同的内容
    console.log(a.next())  // { done: true, value: undefined }
    

    生成器

    生成器是一种返回迭代器的函数。通过function后面的*来表明它是一个生成器。

    • yield关键字是es6的新特性,它可以指定调用next方法时的返回值以及调用顺序
    • 每当执行完yield语句,函数就会停止执行,直到再次调用next方法才会继续执行
    • yield关键字只能在生成器内部使用,其他地方会导致语法错误
    function * createIterator(){
       yield 1
       yield 2
    }
    // 生成器返回的是一个迭代器
    const iterator = createIterator()
    console.log(iterator .next())  // { done: false, value: 1 }
    console.log(iterator .next())  // { done: false, value: 2 }
    console.log(iterator 
    .next())  // { done: true, value: undefined }
    

    使用函数表达式:const createIterator = function *(){ yield 1 }但不能使用箭头函数来创建生成器。

    可迭代对象

    (1) Symbol.iterator

    一个数据结构只要具有Symbol.iterator属性,就可以认为是可迭代的

    • 所有的集合对象array、set、map以及string都是可迭代对象,都有默认的迭代器,即Symbol.iterator属性
    • 生成器生成的对象就是可迭代的,生成器会默认为他们的Symbol.iterator属性赋值
    • 通过执行 Symbol.iterator方法 来获取对象的迭代器

    访问数组的默认迭代器:

    const arr = [1, 2, 3]
    // 通过执行数组的 `Symbol.iterator`方法来获取 `arr` 的迭代器
    const arrIterator = arr[Symbol.iterator]()
    
    console.log(arrIterator.next())  // { value: 1, done: false }
    console.log(arrIterator.next())  // { value: 2, done: false }
    console.log(arrIterator.next())  // { value: 3, done: false }
    console.log(arrIterator.next())  // { value: undefined, done: true }
    
    (2) for...of

    for...of通过对象的Symbol.iterator方法来获取迭代器,并在每次循环中调用可迭代对象的next方法,将迭代器返回对象中的value赋给中间变量,一直循环到done属性为true

    const arr = [1, 2, 3]
    for(let item of arr){
        console.log(item)
    }
    // 1 2 3
    
    (3) 创建可迭代对象

    可以通过给一个不可迭代对象设置Symbol.iterator属性,使它变成可迭代的。Symbol.iterator必须是迭代器生成函数,否则使用for...of会报错。

    // es5
    const list = {
      items: [1,2,3],
      [Symbol.iterator](){
        let index = 0;
        return {
          next: () =>{
            return {
              done: index > this.items.length - 1,
              value: this.done ? undefined : this.items[index++]
            }
          }
        }
      }
    }
    for (const item of list) {
      console.log(item)
    }
    // 1 2 3
    
    // es6
    const list = {
      items: [1,2,3],
      *[Symbol.iterator](){
        for(item of this.items){
          yield item
        }
      }
    }
    for (const item of list) {
      console.log(item)
    }
    // 1 2 3
    
    (3) 内置迭代器

    entries、values、keys都是es6新增的迭代器。下表为各种集合类型的各种迭代器在for...of循环中的中间变量:

    集合类型/迭代器 entries values keys
    array [[index, value]] [value] [index]
    map [[key, value]] [value] [key]
    set [[value, value]] [value] [value]

    每个集合类型都有默认的迭代器,在for... of循环中,如果没有显式的指定,则使用默认的迭代器。上表中的✔表示各集合类型的for... of默认迭代器

    const arr = [1,2,3]
    // 没有显示指定迭代器,则使用array默认的迭代器,即values迭代器
    for (const item of arr) {
      console.log(item)
    }
    // 1 2 3
    
    // 使用显示指定的entries迭代器
    for (const item of arr.entries()) {
      console.log(item)
    }
    // [0,1]  [1,2]  [2,3]
    

    需要注意的是:entries、values、keys请勿与Object.entries、Object.values、Obejct.keys混淆。前者返回的是迭代器,用于for...of循环;后者返回的是根据原对象格式化后的数组

    (4) 字符串迭代器

    es5中也可以使用for循环遍历字符串,但却以编码为单元而非字符,因此无法正确访问双字节字符es6中全面支持unicode,所以for...of循环就可以正确遍历字符串中的双字节字符

    const str = '𠮷1'
    for (let i = 0; i < text.length; i++) {
      console.log(text[i]);
    }
    // " "
    // " "
    // "1"
    
    // 可以正确识别双字节字符
    for (let i of text) {
      console.log(i);
    }
    // "𠮷"
    // "1"
    
    (5) NodeList迭代器

    DOM标准中有一个NodeList类型,用于表示页面文档元素的集合。它含有length属性;可以通过[number]访问元素,跟数组的格式和操作方法很类似,但其实它是对象,也就是我们俗称的伪数组,比如:{0: domObject, 1: domObject, 2: domObject, length:3}
    es6NodeList类型也拥有了默认迭代器,可以使用for...of迭代

    const nodeList = document.getElementsByClassName('abc')
    for (const node of nodeList) {
        console.log(node)
    }
    
    (6) 展开运算符

    展开运算符可以操作所有可迭代对象,并根据默认迭代器来选去要引用的值,然后读取所有值

    const nodeList = document.getElementsByClassName('abc')
    console.log([...nodeList])   
    // [node, node, node]
    
    const map = new Map()
    map.set('name','li yang')
    map.set('age', 18)
    console.log([...map])  // 调用了map的默认迭代器 entries
    // [["name", "li yang"], ["age", 18]]
    

    由于展开运算符可以作用于任意可迭代对象,因此要将可迭代对象装换成数组,这是最简单的方法

    相关文章

      网友评论

        本文标题:深入理解ES6 ---- 迭代器和生成器

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