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循环继续执行
原文地址:https://www.cnblogs.com/qq3279338858/p/12365585.html