Iterator(遍历器)的概念
遍历器(Iterator)是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)。
Iterator 的作用有三个:
1. 为各种数据结构,提供一个统一的、简便的访问接口
2. 使得数据结构的成员能够按某种次序排列
3. ES6 创造了一种新的遍历命令for...of
循环,Iterator 接口主要供for...of
消费。
遍历器对象本质上,就是一个指针对象,每次调用指针对象的
next
方法,依次返回数据结构的每个成员
下面是一个模拟next
方法返回值的例子
function makeIterator(arr) {
let index = 0;
return {
next: function() { // 闭包
return {
value: arr[index++], done: index === arr.length
}
}
}
}
let array = [5, 3, 1];
let result = makeIterator(array);
默认Iterator接口
一种数据结构只要部署了Iterator接口,我们就称这种数据结构是“可遍历的”(iterable)。
ES6 规定,默认的 Iterator 接口部署在数据结构的Symbol.iterator属性([Symbol.iterator]
)
const obj = {
[Symbol.iterator] : function () {
return {
next: function () {
return {
value: 1,
done: true
};
}
};
}
};
上面代码中,对象obj是可遍历的(iterable),因为具有Symbol.iterator
属性。执行这个属性,会返回一个遍历器对象。该对象的根本特征就是具有next
方法。每次调用next
方法,都会返回一个代表当前成员的信息对象,具有value
和done
两个属性。
ES6的有些数据结构原生具备Iterator接口(比如数组),即不用任何处理,就可以被for...of
循环遍历。原因在于,这些数据结构原生部署了Symbol.iterator属性
,另外一些数据结构没有(比如对象)。凡是部署了Symbol.iterator
属性的数据结构,就称为部署了遍历器接口。调用这个接口,就会返回一个遍历器对象。
原生具备Iterator接口的数据结构如下。
- Array
- Map
- Set
- String
- TypedArray
- 函数的arguments对象
- NodeList对象
对于类似数组的对象(存在数值键名和length属性),部署 Iterator 接口,有一个简便方法,就是Symbol.iterator方法直接引用数组的 Iterator 接口。
let iterable = {
0: 'a',
1: 'b',
2: 'c',
length: 3,
[Symbol.iterator]: Array.prototype[Symbol.iterator]
};
for (let item of iterable) {
console.log(item); // 'a', 'b', 'c'
}
调用 Iterator 接口的场合
有一些场合会默认调用 Iterator 接口(即Symbol.iterator
方法),除了下文会介绍的for…of循环,还有几个别的场合。
- 解构赋值
- 扩展运算符
yield*
for...of
循环
for...of
循环可以使用的范围包括数组、Set 和 Map 结构、某些类似数组的对象(比如arguments
对象、DOM NodeList 对象)、Generator对象,以及字符串。
for...of
循环内部调用的是数据结构的Symbol.iterator
方法。
JavaScript原有的for...in
循环,只能获得对象的键名,不能直接获取键值。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结构进行遍历时要注意两点:
1. 遍历的顺序是按照各个成员被添加进数据结构的顺序
2. Set结构遍历时,返回的是一个值,而Map结构遍历时,返回的是一个数组,该数组的两个成员分别为当前Map成员的键名和键值。
对象因为没有部署Iterator接口,所以不能使用for...of
循环
与其他遍历语法的比较
最原始的写法就是for
循环
for (var index = 0; index < myArray.length; index++) {
console.log(myArray[index]);
}
forEach
使用更加简便,但是无法使用break
或return
中途跳出循环
myArray.forEach(function (value) {
console.log(value);
});
for...in
可以遍历数组的键名,主要是为遍历对象而设计的,不适用于遍历数组。
for (var index in myArray) {
console.log(myArray[index]);
}
for...of
循环相比上面几种做法,有一些显著的优点。
- 有着同for...in
一样的简洁语法,但是不像for...in
那样存在遍历顺序不确定、遍历原型链的属性等缺点
- 不同于forEach
方法,它可以与break
、continue
和return
配合使用。
- 提供了遍历所有数据结构的统一操作接口。