ES6 —(Iterator 和 for...of)

来源:互联网 发布:三体歌者文明 知乎 编辑:程序博客网 时间:2024/05/17 04:00

1、Iterator(遍历器)的概念

  JavaScript 共有四种数据集合:数组Array、对象Object、Map、Set。后两个是 ES6 添加的。
  Iterator 是一种接口机制,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署了 Iterator 接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)。
  Iterator 的作用有三个:

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

Iterator 的遍历过程如下:
  (1)创建一个指针对象,指向当前数据结构的起始位置。也就是说,遍历器对象本质上,就是一个指针对象。
  (2)第一次调用指针对象的 next 方法,可以将指针对象指向数据结构的第一个成员。
  (3)第二次调用指针对象的 next 方法,指针对象就指向数据结构的第二个成员。
  (4)不断调用指针对象的 next 方法,直到指针对象指向数据结构的结束位置。
  每一次调用 next 方法,都会返回数据结构的当前成员的信息。具体来说,就是返回一个包含 value 和 done 两个属性的对象。其中 value 属性是当前成员的值,done 属性是一个布尔值,表示遍历是否结束。

2、默认 Iterator 接口

  当使用 for...of 循环遍历某种数据结构时,该循环会自动去寻找 Iterator 接口。一种数据结构只要部署了 Iterator 接口,我们就称这种数据结构是“可遍历的”(Iterable)。
  ES6 规定,默认的 Iterator 接口部署在数据结构的 Symbol.iterator 属性上。或者说,一个数据结构只要具有 Symbol.iterator 属性,就可以认为是可遍历的。Symbol.iterator 属性本身是一个函数,就是当前数据结构默认的遍历器生成函数。执行这个函数,就会返回一个遍历器。至于属性名 Symbol.iterator 它是一个表达式,返回Symbol 对象的 iterator 属性,这是一个预定义好的、类型为 Symbol 的特殊值,所以要放在方括号内。
  ES6 的有些数据结构具备 Iterator 接口,即不用任何处理,就可以被 for...of 循环遍历。凡是部署了 Symbol.iterator 属性的数据结构,就被称为部署了变量器接口。调用这个接口,就会返回一个遍历器对象。

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

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

    let arr = ['a','b','c'];let iter = arr[Symbol.iterator]();console.log(iter.next()); // Object {value: "a", done: false}console.log(iter.next()); // Object {value: "b", done: false}console.log(iter.next()); // Object {value: "c", done: false}console.log(iter.next()); // Object {value: undefined, done: true}

  不具备 Iterator 接口的数据结构(主要是对象),都需要自己在 Symbol.iterator 属性上面部署遍历器生成方法,这样才会被 for...of 循环遍历。例如下面通过遍历器实现指针结构的例子。

function Obj(value){    this.value = value;    this.next = null;}Obj.prototype[Symbol.iterator] = function(){    var iterator = {next: 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, value: undefined};        }    }    return iterator;}var one = new Obj(1);var two = new Obj(2);var three = new Obj(3);one.next = two;two.next = three;for(let i of one){    console.log(i);}

上述代码,首先在构造函数的原型链上部署 Symbol.iterator 方法,调用该方法会返回遍历器对象 iterator ,调用该对象的 next 方法,在返回一个值的同时,自动将内部指针移到下一个实例。
注意:如果 Symbol.iterator 属性对象的不是一个遍历器生成函数(即会返回一个遍历器对象)解释引擎会报错。

3、调用 Iterator 接口的场合

  有一些场合会默认调用 Iterator 接口(即 Symbol.iterator 方法),如下:
(1)解构赋值
  对数组和 Set 结构进行解构赋值时,会默认调用 Symbol.iterator 方法。
(2)扩展运算符
  扩展运算符(...)也会调用默认的 Iterator 接口。
(3)yield*
  yield* 后面跟的是一个可遍历的结构,它会调用该结构的遍历器接口。
(4)for…of
  for...of也会调用默认的 Iterator 接口。
(5)其他场合
  由于数组的遍历会调用遍历器接口,所以任何接受数组作为参数的场合,其他都调用了遍历器接口,如:

  • Array.form()
  • Map(), Set(), WeakMap(), WeakMap()(比如: new Map([[‘a’, 1], [‘b’, 2]]))
  • Promise.all()
  • Promise.race()

4、字符串的 Iterator 接口

  字符串是一个类似数组的对象,也原生具有 Iterator 接口。

var str = "hi";console.log(typeof str[Symbol.iterator]); // functionvar ite = str[Symbol.iterator](); console.log(ite.next()); // Object {value: "h", done: false} console.log(ite.next()); // Object {value: "i", done: false}console.log(ite.next()); // Object {value: undefined, done: true}

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

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

for(let i of arr){    break;}for(let i of arr){    continue;}for(let i of arr){    throw new Error();}

注意:return 方法必须返回一个对象,这是 Generator 规格决定的。
  throw方法主要是配合 Generator 函数使用,一般的遍历器对象用不到这个方法。

6、for…of 循环

  ES6 引入 for...of 循环,作为遍历所有数据结构的统一方法。
  一个数据结构只要部署了 Symbol.iterator 属性,就被视为具有 iterator 接口,就可以用 for...of 循环遍历成员,即 for...of 循环内部调用的是数据结构的 Symbol.iterator 方法。
   for...of 循环可以使用的范围包括数组、Set 和 Map 结构、某些类似数组的对象(如 arguments 对象、DOM NodeList 对象)、Generator 对象,以及字符串。

(1)数组
  数组原生具备 Iterator 接口(即默认部署了 Symbol.iterator 属性)。
   for...of 循环可以替代数组实例的 forEach 方法。
   for...in 循环,只能获得对象的键名,不能直接获取键值,而 for...of 循环,允许遍历获得键值。并且, for...of 循环调用遍历接口,数组的遍历器只返回具有数字索引的属性,而 for...in 能返回非数字键名。

(2)Set 和 Map 结构
  Set 和 Map 结构原生具备 Iterator 接口(即默认部署了 Symbol.iterator 属性),可以直接使用 for...of 循环。

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

(3)计算生成的数据结构
  调用 entries()、 keys()、 values() 方法生成的遍历器对象。for...of 所遍历的都是计算生成的数据结构。

(4)类似数组的对象
  类似数组的对象包括好几类,如 字符串、DOM NodeList 对象、arguments 对象等。

注意:for...of 循环在遍历字符串是能正确识别 32 位的 UTF-16 字符。如果类似数组的对象没有 Iterator 接口,可以使用 Array.from 方法将其转为数组。

(5)对象
  对于普通的对象,for...of 结构不能直接使用,会报错,必须部署了 Iterator 接口后才能使用。但是,这样情况下,for...in 循环依然可以用来遍历键名。
  一种解决方法是,使用 Object.keys 方法将对象的键名生成一个数组,然后遍历这个数组。

for(var key of Object.keys(obj)){    console.log(`${key}: ${obj[key]}`);}

  另一种方法是使用 Generator 函数将对象重新包装一下。

function* entries(obj){    for(let key of Object.keys(obj)){        yield [key, obj[key]];    }}for(let [key, value] of entries(obj)){    console.log(key, value);}

阮一峰:ECMAScript 6入门

原创粉丝点击