您的位置:首页 > 其它

ES6 —(Iterator 和 for...of)

2017-08-29 08:50 375 查看

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]); // function
var 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入门
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: