迭代器

82 阅读3分钟

迭代器的英文是iteration,在软件开发领域,迭代的意思是按照顺序反复执行一段程序,通常有明确的终止条件。ECMAScript6规范新增咯两个高级特性:迭代器和生成器,今天先说迭代器。

循环

计数循环就是一种最简单的迭代,一般我们用得比较多的是数组循环:

        let arr = [1,2]
        for (let i = 0; i < arr.length; index++) {
            console.log(arr[index]);
        }

循环是迭代机制的基础。

当你用这种方式迭代的时候会有一个缺点,就是事先必须知道如何使用数据结构。比如数组我只能通过索引找到数组的某一位,这种情况并不适用于全部的数据结构,比如我们熟悉的Map。

        let map = new Map([
            ["apples", 500],
            ["bananas", 300],
            ["oranges", 200]
        ])
        for (let i = 0; i < map.length; index++) {
            console.log(i);
        }
        //1
        //2
        //3

像Map就无法直接获取到准确的键值。有人会说,用foreach。

        map.forEach((value, key) => {
            console.log(value, key);
        });
        //['apples', 500]
        //['bananas', 300]
        //['oranges', 200]

确实,ES5的foreach可以循环出每一个键值,但还存在一个问题: 没办法标识循环何时终止

为了实现此目的需要用到比较其他的辅助结构,导致代码量的增多,在ECMAScript6之后推出了迭代器模式。

迭代器模式

指的是有些结构称为“可迭代对象(iterable),因为他们实现了正式的Iterable接口,然后通过迭代器Iterator使用。

这样来说可能有点抽象,可以理解为数组或集合是可迭代对象,迭代器就是迭代它们的对象。而实现Iterable接口称为可迭代协议。

如何实现Iterable接口?需要暴露一个属性Symbol.iterator作为默认迭代器。值为一个迭代器工厂函数,调用它返回一个新迭代器,也就一个对象。

map[Symbol.iterator]
//ƒ entries() { [native code] }
map[Symbol.iterator]()
//MapIterator {'apples' => 500, 'bananas' => 300, 'oranges' => 200}
map.entries() //map自带的entries() 方法返回一个带有 Map 中 [key,values] 的迭代器对象,和调用工厂函数的结果是一样的。
MapIterator {'apples' => 500, 'bananas' => 300, 'oranges' => 200}

实际开发中不需要显示调用这个工厂函数来生成迭代器,实现来可迭代协议的所有类型都会自动兼容接收可迭代对象的任何语言特性。人话就是不需要调用函数,直接循环这个结构就完事了。

        for (const x of map) {
            console.log(x);
        }
        for (const x of map.entries()) {
            console.log(x);
        }
        //['apples', 500]
        //['bananas', 300]
        //['oranges', 200]

当然,这只是后台帮我们自动调用了这个工厂函数,跟foreach是一个道理,我们还是无法看到循环何时终止。

迭代器协议

迭代器是一种一次性使用的对象,它会关联到可迭代对象,使用next方法遍历数据。

可以这样理解,迭代器是一个对象,它里面有一个next方法:

         next: () => {
              return { value: 1, done: false }
         }

方法返回两个属性:done和value。done是个布尔值,表示是否还能再次调用next获取值,也就是是否终止。value为可迭代对象下一个值。注意,是下一个。

        let arr = ['foo','bar']
        let iter = arr[Symbol.iterator]()
        console.log(iter);  //Array Iterator {} //迭代器工厂函数
        console.log(iter.next()); //{value: 'foo', done: false}
        console.log(iter.next()); //{value: 'bar', done: false}
        console.log(iter.next()); //{value: undefined, done: true}

我们也可以自定义迭代器:

       const iterableObj = {
            arr: [1, 2, 3],
            [Symbol.iterator]: function () {
                let index = 0
                return {
                    next: () => {
                        if (index < this.arr.length) {
                            return { value: this.arr[index++], done: false }
                        } else {
                            return { value: undefined, done: true }
                        }
                    }
                }
            }
        }

        for (const iterator of iterableObj) {
            console.log(iterator)
        }
        //1 2 3

END