迭代器

81 阅读5分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第N天,点击查看活动详情

可迭代对象与迭代器

实现了正式的Iterable接口,并且可以通过迭代器Iterator消费的对象。

iterator是按需创建的一次性对象,每个迭代器都会关联一个可迭代器对象,而迭代器会暴露迭代器所关联的可迭代器对象的API。

迭代器无需了解与其关联的可迭代对象的结构,只需要知道如何取得连续的值。

Iterator接口【可迭代协议】的实现

要实现的功能:

支持迭代的自我识别能力。创建实现Iterator接口的对象的能力

在ES中,就意味着:要使用以Symbol.iterator作为键的属性,来暴露一个引用迭代器工厂函数的属性。

迭代器工厂函数必须返回一个迭代器

如何检测是否为可迭代对象

检测[Symbol.iterator]属性是否为undefined

哪些语言特性需要可迭代对象

  • for-of
  • 数组解构
  • 扩展操作符
  • Array.from
  • 创建集合、映射
  • Promise.all()接收由期约组成的可迭代对象
  • Promise.race()接收由期约组成的可迭代对象
  • yield*操作符,在生成器中使用

这些原生语言结构都会在后台调用提供的可迭代对象的这个工厂函数,从而创建一个迭代器。

而且这些结构在迭代器上也创建了迭代器接口【Symbol.iterator】函数。Symbol.iterator属性会返回与原有结构相同的迭代器,所以也可以通过迭代器的迭代器去对原有结构中的内容进行访问。

此时使用for-of便可对原有结构或是通过迭代器对原有结构遍历

迭代器协议

迭代器是一种一次性使用的对象,用于迭代与其关联的可迭代对象。

迭代器API使用next方法在可迭代对象中遍历数据。每次成功调用next都会返回一个IteratorResult对象,该对象包含迭代器返回的下一个值。若是不调用next便无法得到迭代器的当前位置。

迭代器并不与可迭代对象某个时刻的快照绑定,而仅仅是使用游标来记录遍历可迭代对象的历程。

如果可迭代对象在迭代期间被修改了,那么迭代器也会反映相应的变化。

由于迭代器维护着一个指向可迭代对象的引用,所以迭代器会阻止垃圾回收程序回收可迭代对象。

IteratorResult对象

有done和value两个属性,

done为Boolean,表示是否还可以再次调用next来取到下一个值

value包含可迭代对象的下一个值【done为false】,或者undefined【done为true】


// 可迭代对象

let arr = ['foo', 'bar'];

// 迭代器工厂函数

console.log(arr[Symbol.iterator]); // f values() { [native code] }

//迭代器

let iter = arr[Symbol.iterator]();

console.log(iter); // ArrayIterator {}

// 执行迭代

console.log(iter.next()); // { done: false, value: 'foo' }

console.log(iter.next()); // { done: false, value: 'bar' }

console.log(iter.next()); // { done: true, value: undefined }

自定义迭代器

迭代器肯定要满足迭代器协议

1.Symbol.iterator属性为一个迭代器工厂函数,该函数返回迭代器对象。

2.迭代器是一个一次性使用的对象

3.API使用next方法在可迭代对象中遍历数据

4.由于一般迭代器也实现了迭代器工厂函数,而且返回就是迭代器本身,所以也要写迭代器的Symbol.iterator属性,并返回this

所以首先要将对象中的Symbol.iterator属性设置为一个函数,函数返回一个对象,用于做迭代器。

迭代器对象中写next函数,函数返回对象有done和value

let log = console.log

class MyArray {

    constructor(length) {

        this.length = length;

        for (let i = 0; i < length; i++) {

            this[i] = i;

        }

    }

    [Symbol.iterator]() {

        let count = 0;//可迭代次数

        let _self = this

        return {

            next() {

                return {

                    done: count >= _self.length,

                    value : _self[count++]

                };

            },

            [Symbol.iterator]() {//迭代器的迭代器工厂函数接口

                return this;

            }

        };

    }

}

let a = new MyArray(10);

let iter = a[Symbol.iterator]();

log("-------------")

for (let i = iter.next(); 
!i.done;
i = iter.next());

log(i.value)

迭代器的提前终止器

迭代器对象可选的 return()方法用于指定在迭代器提前关闭时执行的逻辑。

提前关闭迭代器的情况

  • for-of 循环通过 break、continue、return 或 throw 提前退出;

  • 解构操作并未消费所有值

如何书写return

return函数必须返回一个有效的IteratorResult对象,而该对象的done得设置为true代表终止迭代。

return() {

    return {

        done: true,

        value : _self[count]

    }

}

return函数与迭代器是否可关闭

迭代器是否可关闭与迭代器是否有return无关,也就是说给一个 不可关闭的迭代器 添加正确的return属性并不能使得迭代器变得可关闭

但是一般不可关闭迭代器的 return 属性不是函数对象

关闭与否的效果

在遍历迭代器【不是遍历可迭代对象】时,第一次遍历在结束前就停止了,下一次遍历迭代器时仍然会从之前结束处开始,而不是从头开始。这种迭代器叫做不可关闭的迭代器

如何实现可关闭迭代器

由于每一次进行for-of等操作时会先执行迭代目标的Symbol.iterator函数,而可关闭迭代器就是每次遍历迭代器时,遍历从头开始,所以我们只需要在Symbol.iterator返回迭代器前重置所有迭代器状态为初始状态即可。

[Symbol.iterator](){

    count=0

    return this;

},