美文网首页
ES6学习笔记(17)之 Iterator 和 for...of

ES6学习笔记(17)之 Iterator 和 for...of

作者: 苦苦修行 | 来源:发表于2019-09-30 10:23 被阅读0次

    参考:ECMAScript 6 入门

    简单介绍:

    我的理解:所谓可遍历,就是可以取到下一个。重点在于实现一个可以取到下一个对象的next方法

    首先,Iterator适合于容器对象,既可以存储其它对象的对象,比如:Array, Map, Set等。
    其内部通过next方法可以不断获取存储的里面的下一个对象,实现了这样的next方法的对象便是可遍历的。
    严谨一点,一种数据结构只要部署了 Iterator 接口,我们就称这种数据结构是“可遍历的”(iterable)。

    Iterator接口应该长什么样儿的?使用TypeScript的写法如下:

    interface Iterable {
      [Symbol.iterator]() : Iterator,
    }
    
    interface Iterator {
      // next方法是重点,通过next方法来获取下一个对象
      next(value?: any) : IterationResult,
    }
    
    interface IterationResult {
      value: any,
      done: boolean,
    }
    

    ES6 创造了一种新的遍历命令for...of循环,Iterator 接口主要供for...of消费。当使用for...of循环遍历某种数据结构时,该循环会自动去寻找 Iterator 接口。一个数据结构只要部署了Symbol.iterator属性,就被视为具有 iterator 接口,就可以用for...of循环遍历它的成员。也就是说,for...of循环内部调用的是数据结构的Symbol.iterator方法。

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

    Array
    Map
    Set
    String
    TypedArray
    函数的 arguments 对象
    NodeList 对象
    

    这些数据结构原生部署了Symbol.iterator属性

    let arr = ['a', 'b', 'c'];
    let iter = arr[Symbol.iterator](); //调用Array对象的Symbol.iterato属性方法生成遍历器
    
    iter.next() // { value: 'a', done: false }
    iter.next() // { value: 'b', done: false }
    iter.next() // { value: 'c', done: false }
    iter.next() // { value: undefined, done: true }
    
    Screen Shot 2019-09-09 at 3.45.01 PM.png

    除了for...of,还有哪些是使用iterator的变种(请注意,Object对象默认没有部署 Iterator 接口)

    • 解构赋值:对数组和 Set 结构进行解构赋值时,会默认调用Symbol.iterator方法。
    let set = new Set().add('a').add('b').add('c');
    
    let [x,y] = set;
    // x='a'; y='b'
    
    let [first, ...rest] = set;
    // first='a'; rest=['b','c'];
    
    • 扩展运算符:扩展运算符(...)也会调用默认的 Iterator 接口。
    // 例一
    var str = 'hello';
    [...str] //  ['h','e','l','l','o']
    
    // 例二
    let arr = ['b', 'c'];
    ['a', ...arr, 'd']
    // ['a', 'b', 'c', 'd']
    
    • yield:yield后面跟的是一个可遍历的结构,它会调用该结构的遍历器接口。
    let generator = function* () {
      yield 1;
      yield* [2,3,4];
      yield 5;
    };
    
    var iterator = generator();
    
    iterator.next() // { value: 1, done: false }
    iterator.next() // { value: 2, done: false }
    iterator.next() // { value: 3, done: false }
    iterator.next() // { value: 4, done: false }
    iterator.next() // { value: 5, done: false }
    iterator.next() // { value: undefined, done: true }
    
    • 字符串对象也实现了 Iterator 接口(我的理解,字符串本质上就是一个char数组,所以它可以被遍历也是情理之中的了)
    var someString = "hi";
    var iterator = someString[Symbol.iterator]();
    
    iterator.next()  // { value: "h", done: false }
    iterator.next()  // { value: "i", done: false }
    iterator.next()  // { value: undefined, done: true }
    

    使用 Generator 函数实现遍历接口

    let myIterable = {
      [Symbol.iterator]: function* () {
        yield 1; // yield 是产出的意思
        yield 2;
        yield 3;
      }
    }
    [...myIterable] // [1, 2, 3]
    
    // 或者采用下面的简洁写法
    
    let obj = {
      * [Symbol.iterator]() {
        yield 'hello';
        yield 'world';
      }
    };
    
    for (let x of obj) {
      console.log(x);
    }
    // "hello"
    // "world"
    

    其它遍历器对象:

    let map = new Map().set('a', 1).set('b', 2);
    for (let pair of map) {
      console.log(pair);
    }
    // ['a', 1]
    // ['b', 2]
    

    像上面的例子,map应该调用了它默认的遍历生成器。如果我只想遍历map的key或者value怎么办呢?
    答案是通过:entries(), keys(), values()等。

    ES6 的数组、Set、Map 都部署了以下三个方法,调用后都返回遍历器对象。

    • entries() 返回一个遍历器对象,用来遍历并返回[键名, 键值]组成的数组。对于数组,键名就是索引值;对于 Set,键名与键值相同。Map 结构的 Iterator 接口,默认就是调用entries方法。
    • keys() 返回一个遍历器对象,用来遍历所有的键名。
    • values() 返回一个遍历器对象,用来遍历所有的键值。

    类数组对象举例:

    let arrayLike = { length: 2, 0: 'a', 1: 'b' };//类数组对象
    
    // 报错
    for (let x of arrayLike) {
      console.log(x);
    }
    
    // 正确,先使用Array.from将其转为带 Iterator 接口的数组
    for (let x of Array.from(arrayLike)) {
      console.log(x);
    }
    

    无法中途跳出forEach循环,break命令或return命令都不能奏效。


    非重点:

    遍历器对象的 return(),throw()
    遍历器对象除了具有next方法,还可以具有return方法和throw方法。如果你自己写遍历器对象生成函数,那么next方法是必须部署的,return方法和throw方法是否部署是可选的。
    return方法的使用场合是,如果for...of循环提前退出(通常是因为出错,或者有break语句),就会调用return方法。如果一个对象在完成遍历前,需要清理或释放资源,就可以部署return方法。

    function readLinesSync(file) {
      return {
        [Symbol.iterator]() {
          return {
            next() {
              return { done: false };
            },
            return() {
              file.close();
              return { done: true };
            }
          };
        },
      };
    }
    

    上面代码中,函数readLinesSync接受一个文件对象作为参数,返回一个遍历器对象,其中除了next方法,还部署了return方法。下面的两种情况,都会触发执行return方法。

    // 情况一
    for (let line of readLinesSync(fileName)) {
      console.log(line);
      break;
    }
    
    // 情况二
    for (let line of readLinesSync(fileName)) {
      console.log(line);
      throw new Error();
    }
    

    上面代码中,情况一输出文件的第一行以后,就会执行return方法,关闭这个文件;情况二会在执行return方法关闭文件之后,再抛出错误。
    注意,return方法必须返回一个对象,这是 Generator 规格决定的。
    throw方法主要是配合 Generator 函数使用,一般的遍历器对象用不到这个方法。


    相关文章

      网友评论

          本文标题:ES6学习笔记(17)之 Iterator 和 for...of

          本文链接:https://www.haomeiwen.com/subject/jmxjyctx.html