您的位置:首页 > 其它

ES6学习笔记(七)Iterator和for...of循环

2016-09-30 21:13 267 查看
一、遍历器的感念

JavaScript原有的表示“集合”的数据结构,主要是数组(Array)和对象(Object),ES6又添加了Map和Set。这样就有了四种数据集合;这样就需要一种统一的接口机制,来处理所有不同的数据结构。

遍历器(Iterator)就是这样一种机制。它是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署Iterator接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)。

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

遍历过程首先 创建一个指针对象(遍历器本质),指向当前数据结构的起始位置。依次调用指针对象的next方法,可以将指针指向数据结构的第几个成员。每一次调用next方法,都会返回数据结构的当前成员的信息。具体来说,就是返回一个包含value和done两个属性的对象。其中,value属性是当前成员的值,done属性是一个布尔值,表示遍历是否结束。

当使用for…of循环遍历某种数据结构时,该循环会自动去寻找Iterator接口。

在ES6中,有三类数据结构原生具备Iterator接口:数组、某些类似数组的对象、Set和Map结构。对于这三类数据结构,不用自己写遍历器生成函数,for…of循环会自动遍历它们。除此之外,其他数据结构(主要是对象)的Iterator接口,都需要自己在Symbol.iterator属性上面部署,这样才会被for…of循环遍历。

一个对象如果要有可被for…of循环调用的Iterator接口,就必须在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};
} else {
return {done: true, value: undefined};
}
}
}

function range(start, stop) {
return new RangeIterator(start, stop);
}

for (var value of range(0, 3)) {
console.log(value);
}


上面代码是一个类部署Iterator接口的写法。Symbol.iterator属性对应一个函数,执行后返回当前对象的遍历器对象。

二、for…of 循环

for…of循环可以使用的范围包括数组、Set和Map结构、某些类似数组的对象(比如arguments对象、DOM NodeList对象)、后文的Generator对象,以及字符串。

数组原生具备iterator接口,for…of循环本质上就是调用这个接口产生的遍历器,可以用下面的代码证明。

const arr = ['red', 'green', 'blue'];
let iterator  = arr[Symbol.iterator]();

for(let v of arr) {
console.log(v); // red green blue
}

for(let v of iterator) {
console.log(v); // red green blue
}


for…of循环可以代替数组实例的forEach方法。

for…in循环读取键名,for…of循环读取键值: JavaScript原有的for…in循环,只能获得对象的键名,不能直接获取键值。ES6提供for…of循环,允许遍历获得键值。

var arr = ['a', 'b', 'c', 'd'];

for (let a in arr) {
console.log(a); // 0 1 2 3
}

for (let a of arr) {
console.log(a); // a b c d
}


Set和Map结构也原生具有Iterator接口,可以直接使用for…of循环。

var engines = new Set(["Gecko", "Trident", "Webkit", "Webkit"]);
for (var e of engines) {
console.log(e);
}
// Gecko
// Trident
// Webkit

var es6 = new Map();
es6.set("edition", 6);
es6.set("committee", "TC39");
es6.set("standard", "ECMA-262");
for (var [name, value] of es6) {
console.log(name + ": " + value);
}
// edition: 6
// committee: TC39
// standard: ECMA-262


上面代码演示了如何遍历Set结构和Map结构。值得注意的地方有两个,首先,遍历的顺序是按照各个成员被添加进数据结构的顺序。其次,Set结构遍历时,返回的是一个值,而Map结构遍历时,返回的是一个数组,该数组的两个成员分别为当前Map成员的键名和键值。

有些数据结构是在现有数据结构的基础上,计算生成的。比如,ES6的数组、Set、Map都部署了以下三个方法,调用后都返回遍历器对象。

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

keys() 返回一个遍历器对象,用来遍历所有的键名。

values() 返回一个遍历器对象,用来遍历所有的键值。

这三个方法调用后生成的遍历器对象,所遍历的都是计算生成的数据结构。

let arr = ['a', 'b', 'c'];
for (let pair of arr.entries()) {
console.log(pair);
}
// [0, 'a']
// [1, 'b']
// [2, 'c']


注意:

对于普通的对象,for…of结构不能直接使用,会报错,必须部署了iterator接口后才能使用。但是for…in循环依然可以用来遍历键名(for…in循环主要是为遍历对象而设计的,不适用于遍历数组)。

一种解决方法是,使用Object.keys方法将对象的键名生成一个数组,然后遍历这个数组。

for (var key of Object.keys(someObject)) {
console.log(key + ": " + someObject[key]);
}


在对象上部署iterator接口的代码,参见本章前面部分。一个方便的方法是将数组的Symbol.iterator属性,直接赋值给其他对象的Symbol.iterator属性。比如,想要让for…of环遍历jQuery对象,只要加上下面这一行就可以了。

jQuery.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator]


另一个方法是使用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);
}
// a -> 1
// b -> 2
// c -> 3


for…in循环有几个缺点。

数组的键名是数字,但是for…in循环是以字符串作为键名“0”、“1”、“2”等等。

for…in循环不仅遍历数字键名,还会遍历手动添加的其他键,甚至包括原型链上的键。

某些情况下,for…in循环会以任意顺序遍历键名。

下面是一个使用break语句,跳出for…of循环的例子。

for (var n of fibonacci) {
if (n > 1000)
break;
console.log(n);
}


上面的例子,会输出斐波纳契数列小于等于1000的项。如果当前项大于1000,就会使用break语句跳出for…of循环。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: