01 对Iterator(迭代器/遍历器)的初步认识

160 阅读1分钟

1.原生Iterator(以数组为例)

原生默认具备Iterator接口的数据结构如下:

  • Array
  • Set
  • Map
  • String
  • arguments对象
  • NodeList对象

对于这些数据,我们可以直接对其进行遍历。以一个数组为例:

let arr = ['aaa', 'bbb', 'ccc', 'ddd'];

直接用for of遍历

for (const item of arr) {
    console.log(item);
} // 依次输出aaa bbb ccc ddd

获取迭代器,并用next()遍历

// 获取迭代器
let iter = arr[Symbol.iterator]();
// 返回的是迭代器对象
console.log(iter); // Array Iterator
// 用next()遍历
console.log(iter.next()); // {value: 'aaa', done: false}
console.log(iter.next()); // {value: 'bbb', done: false}
console.log(iter.next()); // {value: 'ccc', done: false}
console.log(iter.next()); // {value: 'ddd', done: false}
console.log(iter.next()); // {value: undefined, done: true}

next()返回该数据结构的当前成员的信息,value表示该成员的值,done表示遍历是否结束。

2. 不具备Iterator的对象

如果没有内置的迭代器接口,就从其他地方“偷”过来。但前提是,被迭代的对象必须具有线性的结构,即谁先谁后,必须清楚。(否则怎么知道成员的迭代顺序呢?)在这里,用一个普通对象来模拟一个数组。

let obj = {
    0: 'aaa',
    1: 'bbb',
    2: 'ccc',
    length: 3,
    [Symbol.iterator]: Array.prototype[Symbol.iterator] // 偷了
}

对象本身是非线性的,但给它增加了名为012的属性,因此具备了线性的结构。再加上length属性和从Array原型那里偷来的迭代器接口,这个对象就可以伪装成一个数组了。

现在,可以直接用for of遍历了。

for (const item of obj) {
    console.log(item);
} // 依次打印aaa bbb ccc

3.Iterator的应用场景:防止误改数据

let obj2 = {
    code: 200,
    name: 'obj2',
    list: ['aaa', 'bbb', 'ccc'],
    [Symbol.iterator]() {
        let index = 0;
        return {
            next: () => {
                // return {
                //     value: this.list[index++],
                //     done: index >= (this.list.length + 1) ? true : false
                // };

                // 下面这个更易懂
                return index < this.list.length
                    // ? { value: this.list[index++], done: false }
                    // : { value: undefined, done: true }
                    // 对于迭代器对象来说,done:false和value:undefined属性都是可以省略的
                    // 因此可以写为:
                    ? { value: this.list[index++] } :
                    : { done: true };
            }
        }
    }
}

多数情况,我们往往只关心obj2这个对象里面存储的数组list是什么,并不愿修改它;其他属性如codename(这里只是随便取名)相比之下并不常用,但又不可删除。

因此我们希望能增添一个保护机制,防止手抖误将数据修改。让数据只可读、不可写。

假设在类创建时,我们已经将codenamelist作为私有属性,这里不再演示。这样的话,就不可以通过obj2.list这种方式访问或修改数据,那么迭代器的作用就显现出来了。在上述代码中,我们已经给obj2部署了Iterator,它现在已经具有了可迭代性。

直接用for of遍历

for (const item of obj2) {
    console.log(item);
} // 依次输出aaa bbb ccc

获取迭代器,并用next()遍历

let iter2 = obj2[Symbol.iterator]();
console.log(iter2); // {next: ƒ}

console.log(iter2.next()); // {value: 'aaa'}
console.log(iter2.next()); // {value: 'bbb'}
console.log(iter2.next()); // {value: 'ccc'}
console.log(iter2.next()); // {done: true}

这里打印输出迭代器对象iter2时,直接得到了手写的next()方法。

而在第一节中,打印输出数组的原生迭代器对象iter时,得到的是Array Iterator,其原型中包含正宗的next()方法,如下图所示。

image.png

使用扩展运算符和Array.from()构建成数组

console.log([...obj2]); // ['aaa', 'bbb', 'ccc']
console.log(Array.from(obj2)); // ['aaa', 'bbb', 'ccc']