sunyongjian / blog

个人博客😝😋😄
664 stars 54 forks source link

迭代器(iterator) #18

Open sunyongjian opened 7 years ago

sunyongjian commented 7 years ago

Iterator

背景

生成器概念在Java,Python等语言中都是具备的,ES6也添加到了JavaScript中。Iterator可以使我们不需要初始化集合,以及索引的变量,而是使用迭代器对象的next方法,返回集合的下一项的值,偏向程序化。

ES5中遍历集合通常都是for循环,数组还有forEach方法,对象就是for-in,而迭代器可以统一处理所有集合数据的方法。迭代器是一个接口,只要你这个数据结构暴露了一个iterator的接口,那就可以完成迭代。ES6创造了一种新的遍历命令for...of循环,Iterator接口主要供for...of消费。

ES5中的loop

什么是迭代器

迭代器是带有特殊接口的对象。含有一个next()方法,调用返回一个包含两个属性的对象,分别是value和done,value表示当前位置的值,done表示是否迭代完,当为true的时候,调用next就无效了。

ES5模拟一个迭代器

  function createIterator(ary) {
    var i = 0;
    return {
      next: function() {
        return {
          value: ary[i++],
          done: i > ary.length
        }
      }
    }
  }
  var iterator = createIterator(['a', 'b', 'c'])
  var done = false;

  while (!done) {
    var result = iterator.next();
    console.log(result);
    done = result.done;
  }
  //{ value: 'a', done: false }
  //{ value: 'b', done: false }
  //{ value: 'c', done: false }
  //{ value: undefined, done: true }

createIterator可以看做一个返回迭代器对象的工厂函数,通过while循环调用返回的iterator.next()会得到result,直到done变为true。用ES5写还是比较麻烦的,而且我们这样写并不支持for...of。很快就会看到ES6真正的写法啦。

timg1

迭代器协议(Iteration protocols)

迭代器对象不是新的语法或新的内置对象,而一种协议( 迭代器协议),所有遵守这个协议的对象,都可以称之为迭代器。也就是说我们上面ES5的写法得到的对象遵循迭代器协议,即包含next,调用next返回一个result{value,done}。

可迭代类型

ES6还引入了一个新的Symbol对象,symbol值是唯一的。定义了一个Symbol.iterator属性,只要对象中含有这个属性,就是可迭代的,可用于for...of。在ES6中,所有的集合对象,包括数组,Map和Set,还有字符串都是可迭代的,因为他们都有默认的迭代器。 不了解Symbol的可以移步Symbol对象是什么

使对象可迭代

前面说了只要对象包含[Symbol.iterator]的属性,就可以通过for...of遍历。我们也可以在对象中添加该属性。

 我们用两种方式
    const obj = {
      b: 2
    }
    const a = 'a'
    obj.a = 1;
    Object.defineProperty(obj, Symbol.iterator, {
      enumerable: false,
      writable: false,
      configurable: true,
      value: function () {
        const that = this;
        let index = 0;
        const ks = Object.keys(that);
        return {
          next: function() {
            return {
              value: that[ks[index++]],
              done: (index > ks.length)
            }
          }
        }
      }
    })
    for(const v of obj) {
      console.log(v); //  2 , 1
    }

通过defineProperty向obj对象中添加[Symbol.iterator],我们在对应的value做的就是通过Object.keys取出它的key,然后调用一次next就往后找一位,可以通过next()尝试一下。因为obj有了[Symbol.iterator],for...of可以找到,并且调用。

    const fibonacci = {
      [Symbol.iterator]: function () {
        let [pre, next] = [0, 1];
        return {
          next() {
            [pre, next] = [next, pre + next];
            return {
              value: next,
              done: next > 1000
            }
          }
        }
      }
    }

    for(var n of fibonacci) {
      console.log(n)
    }
    // 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610,987

通过直接声明给对象定义一个iterator。这里是iterator经典的用法。当这个数据集合特别大,甚至无限的时候,我们要把它定义好,取出来都是很庞大的操作,这时候iterator的优势就很明显了。只有调用一次next,才返回下一个值,不调用就没有。假如说我们给done没加限制条件,这个迭代器就永远没有done=true,就会永远可以next。为了防止for...of ,调用next的时候,有可能让我的电脑卡死,限制在1000以内。

还有一些值得关注的点

总结

ES6提出迭代器的概念,契合了JS语言发展的趋势,统一处理数据结构。迭代器是ES6中很重要的部分,我们仅仅使用是很方便的,但是自定义一些iterator,或者更复杂的方式运行迭代器,还需要我们继续学习。 而定义一个对象的迭代器,又与Symbol对象有关,它采用了Symbol里面的一个默认属性iterator,用来访问对象的迭代器。最后可迭代的数据类型,我们都可以用for...of方法循环遍历,而且集合和字符串内置迭代器,我们可以轻松方便的访问。拓展运算符也是基于iterator的拓展,通过...我们可以把其他数据类型转化为数组,因为...通过执行迭代器,并读取返回的value。

下一篇我们还有未完成的高级迭代器-生成器。

liuyiliuyi commented 7 years ago

for in “其他数组等类型都不行。“那一块写错了吧, 数组也是可以for in 的, 可以取到对应的索引。

sunyongjian commented 7 years ago

@liuyiliuyi 是的,感谢... 已修改。我也不知道我当时为啥写了这句话- -