前端学习笔记--ES6迭代器

143 阅读4分钟

Iterator

一、概念

任何数据结构只要部署 Iterator 接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)。

iterator的三个作用:

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

ES6 的有些数据结构原生具备 Iterator 接口(比如数组),即不用任何处理,就可以被 for...of 循环遍历。原因在于,这些数据结构原生部署了 Symbol.iterator 属性(详见下文),另外一些数据结构没有(比如对象)。凡是部署了 Symbol.iterator 属性的数据结构,就称为部署了遍历器接口。调用这个接口,就会返回一个遍历器对象。

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

  • Array
  • Map
  • Set
  • String
  • TypedArray
  • 函数的 arguments 对象
  • NodeList 对象
 let str = "hello world";
    let iterators = str[Symbol.iterator](); // 有一个Symbol.iterator属性,是一个返回值为迭代器的函数
    console.log(iterators)
    while(true){
        let e = iterators.next();
        if(e.done){
            break;
        }
        console.log(e,e.value);
    }

对于原生部署 Iterator 接口的数据结构,不用自己写遍历器生成函数, for...of 循环会自动遍历它们。除此之外,其他数据结构(主要是对象)的 Iterator 接口,都需要自己在 Symbol.iterator 属性上面部署,这样才会被 for...of 循环遍历。

对象(Object)之所以没有默认部署 Iterator 接口,是因为对象的哪个属性先遍历,哪个属性后遍历是不确定的,需要开发者手动指定。本质上,遍历器是一种线性处理,对于任何非线性的数据结构,部署遍历器接口,就等于部署一种线性转换。不过,严格地说,对象部署遍历器接口并不是很必要,因为这时对象实际上被当作 Map 结构使用,ES5 没有 Map 结构,而 ES6 原生提供了。

一个对象如果要具备可被 for...of 循环调用的 Iterator 接口,就必须在 Symbol.iterator 的属性上部署遍历器生成方法(原型链上的对象具有该方法也可)。

// 手写一个类,实现可迭代
class Range {
        start;
        stop;
        current;
        constructor(start, stop) {
                this.start = start;
                this.stop = stop;
                this.current = start;
            }
            [Symbol.iterator]() {
                return this;
            }
            next(){
                let done = this.current >this.stop;
                let result = {value:this.current,done};
                this.current++;
                return result;
            }
    }
    const range = new Range(2, 10);
    const iter = range[Symbol.iterator]();
    while (true) {
        let e = iter.next();
        if (e.done) {
            break;
        }
        console.log(e.value)
    }

二、调用 Iterator 接口的场合

1.解构赋值

2.扩展运算符

3.yield*

4.字符串的 Iterator 接口

 // 对象的遍历
    var obj = {
        name:"hello",
        age:18,
        sex:'男'
    }
    // for in
    for(let e in obj){
        console.log(e)
    }
    // for of 
    for(let value of obj){  // error 对象不是可迭代的,没有iterator接口
        console.log(value)
    }
    // 数组的遍历
    var arr = ["a","b","c"];
    // for in 

    for(let e in arr){
        console.log(e,typeof e) // 数组是一个对象,这里输出的键值是数组的索引,
                                // 注意key是String类型。对象的键是String类型的。
    }

    for(let e of arr){
        console.log(e)  // 直接输出值
    }

上面代码表明, **for...in 循环读取键名, for...of 循环读取键值。**如果要通过 for...of 循环,获取数组的索引,可以借助数组实例的 entries 方法和 keys 方法。

Set 和 Map 结构:

 let set = new Set();
    set.add("hello");
    set.add("world");
    set.add("computer")

    for (let e of set){
        console.log(e)
    }

    let map = new Map();
    map.set("red","红色");
    map.set("blue","蓝色");
    map.set("green","绿色");
    for(let entry of map){ // next方法value值是数组
        console.log(entry instanceof Array)
        console.log(entry)
    }

    for(let [k,v] of map){
        console.log(k,v)
    }

首先,遍历的顺序是按照各个成员被添加进数据结构的顺序。其次,Set 结构遍历时,返回的是一个值,而 Map 结构遍历时,返回的是一个数组,该数组的两个成员分别为当前 Map 成员的键名和键值。

for...in 循环有几个缺点:

  • 数组的键名是数字,但是 for...in 循环是以字符串作为键名“0”、“1”、“2”等等。
  • for...in 循环不仅遍历数字键名,还会遍历手动添加的其他键,甚至包括原型链上的键。
  • 某些情况下, for...in 循环会以任意顺序遍历键名。

总之, for...in 循环主要是为遍历对象而设计的,不适用于遍历数组。

for...of 循环相比上面几种做法,有一些显著的优点。

  • 有着同 for...in 一样的简洁语法,但是没有 for...in 那些缺点。
  • 不同于 forEach 方法,它可以与 break 、 continue 和 return 配合使用。
  • 提供了遍历所有数据结构的统一操作接口。