zoukankan      html  css  js  c++  java
  • 2020-03-04:各种遍历方法的区别和 Iterator 遍历器

    总结

    • for...infor(let k in A) { ... }
      • 数组遍历只能获得键名(0,1,2,...)
      • 能够遍历普通对象的键名
      • 只能用来遍历对象、数组、字符串
      • 字符串遍历和数组相同只能获得键名
      • 会遍历手动添加的属性和原型继承来的属性
      • 能够退出循环,for循环中continue退出当次循环,break退出之后所有循环。注释:return是不能直接和for配合使用的,只能在函数中打断函数内for循环继续执行
    • for...of
      • 数组遍历只能获得键值
      • 不能够遍历普通对象,但是ES6提供了entries、keys、values、Array.from()返回遍历器对象
      • 能遍历以下数据结构
      • 字符串遍历中能够正确识别 32 位 UTF-16 字符,即把'auD83DuDC0A'这样的字符串识别为a普通字符串和uD83DuDC0A32 位 UTF-16 字符
      • 只会根据 Iterator 接口返回遍历,数组手动添加属性或在原型上添加属性都不会被遍历
      • 一样能够退出循环
    • forEach等其他遍历方法
      • 不能够退出循环
    • 原生具备 Iterator 接口的数据结构如下
      • Array
      • Map(注释:二维数组,可以视为有顺序的对象使用)
      • Set
      • String
      • TypedArray(注释:指一类数据结构,类型化数组)
      • 函数的 arguments 对象
      • NodeList 对象(注释:一个节点的集合,常通过document.querySelectorAll获得)
    • 调用 Iterator 接口的场合
      • 解构赋值let [x,y] = set;
      • 扩展运算符['a', ...arr, 'd']
      • yield*yield* [2,3,4];
      • for...of
      • Array.from() 注释:从一个类似数组或可迭代对象创建一个新的,浅拷贝的数组实例
      • Map(), Set() (比如new Map([['a',1],['b',2]]))注释:Map二维数组,一个数组存键(可以为对象),一个数组存值。先查找键然后通过键的索引查找值。
      • WeakMap(), WeakSet() 注释:相对Map和Set而言,弱引用不会影响垃圾回收
      • Promise.all()
      • Promise.race() 注释:一旦迭代器中的某个promise解决或拒绝,返回的 promise就会解决或拒绝。
    • iterator 遍历器实现
      • 对于类似数组的对象(存在数值键名和length属性),部署 Iterator 接口,有一个简便方法,就是Symbol.iterator方法直接引用数组的 Iterator 接口
      • 如果for...of循环提前退出(通常是因为出错,或者有break语句),就会调用return方法。return方法的使用场合是,一个对象在完成遍历前,需要清理或释放资源,就可以部署return方法。
      • next方法是必须部署的,return方法和throw方法是否部署是可选的
    class LikeArr {
      constructor() {
        this.arr = arguments;
      }
      [Symbol.iterator]() {
        let index = 0;
        const arr = this.arr;
        return {
          next() {
            return {
              value: arr[index++],
              done: index > arr.length
            };
          }
        };
      }
    }
    
    • 生成器的实现(只用于遍历一次,性能更好)
    class RangeIterator {
      constructor(start, stop) {
        this.value = start;
        this.stop = stop;
      }
    
      [Symbol.iterator]() { return this; }
    
      next() {
        var value = this.value;
        if (value < this.stop) {
          this.value++;
          return {done: false, value: value};
        }
        return {done: true, value: undefined};
      }
    }
    

    ————————————————————————————————————————————————————————————————————————————————

    https://es6.ruanyifeng.com/#docs/iterator

    Iterator(遍历器)的概念

    • Iterator 接口主要供for...of消费
    • Iterator 的遍历过程是:
      • 创建一个指针对象,指向当前数据结构的起始位置。
      • 第一次调用指针对象的next方法,可以将指针指向数据结构的第一个成员
      • 不断调用指针对象的next方法,直到它指向数据结构的结束位置。
      • 疑问:指针对象是指创建一个包含内存地址的对象吗?应该是一个普通对象包含value和done两个属性
    • 每一次调用next方法,都会返回包含value和done两个属性的对象
      • value属性是当前成员的值,最后一个可以省略
      • done属性是一个布尔值,表示遍历是否结束。非最后一个时可以省略
      • 注释:最后返回的指针对象为{value:undefined,done:true}
    • 疑问:任何语法结构实际都是函数的一种表现方式?
    • 使用 TypeScript 的写法,遍历器接口(Iterable)、指针对象(Iterator)和next方法返回值的规格可以描述如下(略)

    默认 Iterator 接口

    • 当使用for...of循环遍历某种数据结构时,该循环会自动去寻找 Iterator 接口。
    • 默认的 Iterator 接口部署在数据结构的Symbol.iterator属性
      • 注释:done不一定需要返回布尔值,只需要判定为true时遍历便会停止
      • 注释:最后一个指针对象是不会被遍历的,应该是先判断的done的真假
    • 注释:使用class创建构造函数时,constructor中this定义的属性为当前实例的属性,其他地方定义的为其原型上的属性
    // 方法1
    class LikeArr {
      constructor() {
        const arr = arguments;
        this[Symbol.iterator] = function() {
          let index = 0;
          return {
            next() {
              return {
                value: arr[index++],
                done: index > arr.length
              };
            }
          };
        };
      }
    }
    // 方法2
    class LikeArr {
      constructor() {
        this.arr = arguments;
      }
      [Symbol.iterator]() {
        let index = 0;
        const arr = this.arr;
        return {
          next() {
            return {
              value: arr[index++],
              done: index > arr.length
            };
          }
        };
      }
    }
    // 方法3
    function LikeArr() {
      this.arr = arguments;
    }
    LikeArr.prototype[Symbol.iterator] = function() {
      let index = 0;
      const arr = this.arr;
      return {
        next() {
          return {
            value: arr[index++],
            done: index > arr.length
          };
        }
      };
    };
    
    const likeArr = new LikeArr(1, 3, 5, 6, 7);
    
    • 注释:for...of循环用函数表示
    for (const iterator of likeArr) {
      console.log(iterator);
    }
    
    function forFun(arr) {
      const iteratorObj = arr[Symbol.iterator]();
      const toNext = function(iteratorObj) {
        const iterator = iteratorObj.next();
        if (iterator.done) return;
        console.log(iterator.value);
        toNext(iteratorObj);
      };
      toNext(iteratorObj);
    }
    forFun(likeArr);
    
    • 原生具备 Iterator 接口的数据结构如下
      • Array
      • Map(注释:二维数组,可以视为有顺序的对象使用)
      • Set
      • String
      • TypedArray(注释:指一类数据结构,类型化数组)
      • 函数的 arguments 对象
      • NodeList 对象(注释:一个节点的集合,常通过document.querySelectorAll获得)
    • 对象(Object)之所以没有默认部署 Iterator 接口,是因为对象的哪个属性先遍历,哪个属性后遍历是不确定的,需要开发者手动指定。
      • 疑问:Set是有顺序的?
    • 注释:创建一个具有Iterator的类,注意this的用法
    • 注释:这是一个生成器?适用于需要遍历每一项的大数据,用以节省空间。
    • 注释:该class只能遍历一次,源于它特别的Symbol.iterator返回,而且在遍历时更省内存
    class RangeIterator {
      constructor(start, stop) {
        this.value = start;
        this.stop = stop;
      }
    
      [Symbol.iterator]() { return this; }
    
      next() {
        var value = this.value;
        if (value < this.stop) {
          this.value++;
          return {done: false, value: value};
        }
        return {done: true, value: undefined};
      }
    }
    
    function range(start, stop) {
      return new RangeIterator(start, stop);
    }
    
    for (var value of range(0, 3)) {
      console.log(value); // 0, 1, 2
    }
    
    • 通过遍历器实现指针结构的例子
    • 注释:current = current.next;实现当前指针向下一个对象移动
    • 注释:可以把遍历过程理解为对当前实例next方法的调用结果,而这个当前对象是可以变动的
    function Obj(value) {
      this.value = value;
      this.next = null;
    }
    
    Obj.prototype[Symbol.iterator] = function() {
      var iterator = { next: next };
    
      var current = this;
    
      function next() {
        if (current) {
          var value = current.value;
          current = current.next;
          return { done: false, value: value };
        } else {
          return { done: true };
        }
      }
      return iterator;
    }
    
    var one = new Obj(1);
    var two = new Obj(2);
    var three = new Obj(3);
    
    one.next = two;
    two.next = three;
    
    for (var i of one){
      console.log(i); // 1, 2, 3
    }
    
    • 对于类似数组的对象(存在数值键名和length属性),,部署 Iterator 接口,有一个简便方法,就是Symbol.iterator方法直接引用数组的 Iterator 接口
      • 注释:这里的3:'d'是不会遍历到的,受制于length。同理其他属性也不会被遍历
    let iterable = {
      0: 'a',
      1: 'b',
      2: 'c',
      3: "d",
      length: 3,
      [Symbol.iterator]: Array.prototype[Symbol.iterator]
    };
    for (let item of iterable) {
      console.log(item); // 'a', 'b', 'c'
    }
    

    调用 Iterator 接口的场合

    (1)解构赋值

    • 对数组和 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'];
    

    (2)扩展运算符

    • 提供了一种简便机制,可以将任何部署了 Iterator 接口的数据结构,转为数组
    // 例一
    var str = 'hello';
    [...str] //  ['h','e','l','l','o']
    
    // 例二
    let arr = ['b', 'c'];
    ['a', ...arr, 'd']
    // ['a', 'b', 'c', 'd']
    

    (3)yield*

    • yield*后面跟的是一个可遍历的结构,它会调用该结构的遍历器接口。
      • 疑问:需要对Generator进行深入了解
    • 注释:这里讲的是yield* [2,3,4]在for...of遍历时作为已扩展对象,即遍历了1后,依次遍历2,3,4,而不是把[2,3,4]作为一个遍历对象
    • 注释:generator可以作为一个Symbol.iterator使用
    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 }
    

    (4)其他场合

    • 由于数组的遍历会调用遍历器接口,所以任何接受数组作为参数的场合,其实都调用了遍历器接口。
      • for...of
      • Array.from() 注释:从一个类似数组或可迭代对象创建一个新的,浅拷贝的数组实例
      • Map(), Set() (比如new Map([['a',1],['b',2]]))注释:Map二维数组,一个数组存键(可以为对象),一个数组存值。先查找键然后通过键的索引查找值。
      • WeakMap(), WeakSet() 注释:相对Map和Set而言,弱引用不会影响垃圾回收
      • Promise.all()
      • Promise.race() 注释:一旦迭代器中的某个promise解决或拒绝,返回的 promise就会解决或拒绝。

    字符串的 Iterator 接口

    • 可以覆盖原生的Symbol.iterator方法,达到修改遍历器行为的目的

    Iterator 接口与 Generator 函数

    • Symbol.iterator方法的最简单实现,还是使用下一章要介绍的 Generator 函数
      • 注释:Generator函数返回一个含有next方法的对象
    let myIterable = {
      [Symbol.iterator]: function* () {
        yield 1;
        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"
    

    遍历器对象的 return(),throw()

    • next方法是必须部署的,return方法和throw方法是否部署是可选的
    • 如果for...of循环提前退出(通常是因为出错,或者有break语句),就会调用return方法。return方法的使用场合是,一个对象在完成遍历前,需要清理或释放资源,就可以部署return方法。
    • return方法必须返回一个对象,这是 Generator 规格决定的
      • 注释:chrome 80.0.3987.116 return方法并不一定需要返回
    function readLinesSync(file) {
      return {
        [Symbol.iterator]() {
          return {
            next() {
              return { done: false };
            },
            return() {
              file.close();
              return { done: true };
            }
          };
        },
      };
    }
    
    // 会在执行return方法关闭文件之后,再抛出错误
    for (let line of readLinesSync(fileName)) {
      console.log(line);
      throw new Error();
    }
    

    for...of 循环

    • 注释:Generator对象可以被forof遍历

    数组

    • JavaScript 原有的for...in循环,只能获得对象的键名,不能直接获取键值。ES6 提供for...of循环,允许遍历获得键值。
    • for...of循环调用遍历器接口,数组的遍历器接口只返回具有数字索引的属性。这一点跟for...in循环也不一样。
    let arr = [3, 5, 7];
    arr.foo = 'hello';
    
    for (let i in arr) {
      console.log(i); // "0", "1", "2", "foo"
    }
    
    for (let i of arr) {
      console.log(i); //  "3", "5", "7"
    }
    

    Set 和 Map 结构

    • Map进行for...of循环时,遍历的value是一个长度为2的数组,第一位为key第二位为val
    var es6 = new Map();
    for (var [name, value] of es6) {
      console.log(name + ": " + value);
    }
    

    计算生成的数据结构

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

    类似数组的对象

    • for...of循环还有一个特点,就是会正确识别 32 位 UTF-16 字符。注释:这里uD83DuDC0A是一个32的 UTF-16 字符
    for (let x of 'auD83DuDC0A') {
      console.log(x);
    }
    
    • Array.from() 可以通过以下方式来创建数组对象
      • 伪数组对象(拥有一个 length 属性和若干索引属性的任意对象)
      • 可迭代对象(可以获取对象中的元素,如 Map和 Set 等)
    let arrayLike = { length: 2, 0: 'a', 1: 'b' };
    
    // 报错
    for (let x of arrayLike) {
      console.log(x);
    }
    
    // 正确
    for (let x of Array.from(arrayLike)) {
      console.log(x);
    }
    

    对象

    • 对于普通的对象,for...in循环依然可以用来遍历键名。

    与其他遍历语法的比较

    • 数组无法中途跳出forEach循环,break命令或return命令都不能奏效。
    • for...in循环不仅遍历数字键名,还会遍历手动添加的其他键,甚至包括原型链上的键。
    • 某些情况下,for...in循环会以任意顺序遍历键名。
      • 疑问:对于对象?
    • for...in循环主要是为遍历对象而设计的,不适用于遍历数组
    • for循环中continue退出当次循环,break退出之后所有循环。注释:return是不能直接和for配合使用的,只能在函数中打断函数内for循环继续执行
  • 相关阅读:
    第二次结对作业(陆桂莺+崔亚明)
    第一次结对作业
    第二次作业:代码互改
    markdown详细
    第一次个人编程作业:我的分数我做主
    手动下载transformers的模型
    torch设置GPU
    Python import的搜索路径和不可以import的解决方法 (On Linux)
    Python中windows路径的3种写法
    一台计算机安装多个版本的torch和CUDA的教程
  • 原文地址:https://www.cnblogs.com/qq3279338858/p/12365585.html
Copyright © 2011-2022 走看看