参考:ECMAScript 6 入门
简单介绍:
我的理解:所谓可遍历,就是可以取到下一个。重点在于实现一个可以取到下一个对象的next方法
首先,Iterator适合于容器对象,既可以存储其它对象的对象,比如:Array, Map, Set等。
其内部通过next方法可以不断获取存储的里面的下一个对象,实现了这样的next方法的对象便是可遍历的。
严谨一点,一种数据结构只要部署了 Iterator 接口,我们就称这种数据结构是“可遍历的”(iterable)。
Iterator接口应该长什么样儿的?使用TypeScript的写法如下:
interface Iterable {
[Symbol.iterator]() : Iterator,
}
interface Iterator {
// next方法是重点,通过next方法来获取下一个对象
next(value?: any) : IterationResult,
}
interface IterationResult {
value: any,
done: boolean,
}
ES6 创造了一种新的遍历命令for...of循环,Iterator 接口主要供for...of消费。当使用for...of循环遍历某种数据结构时,该循环会自动去寻找 Iterator 接口。一个数据结构只要部署了Symbol.iterator属性,就被视为具有 iterator 接口,就可以用for...of循环遍历它的成员。也就是说,for...of循环内部调用的是数据结构的Symbol.iterator方法。
原生具备 Iterator 接口的数据结构如下
Array
Map
Set
String
TypedArray
函数的 arguments 对象
NodeList 对象
这些数据结构原生部署了Symbol.iterator
属性
let arr = ['a', 'b', 'c'];
let iter = arr[Symbol.iterator](); //调用Array对象的Symbol.iterato属性方法生成遍历器
iter.next() // { value: 'a', done: false }
iter.next() // { value: 'b', done: false }
iter.next() // { value: 'c', done: false }
iter.next() // { value: undefined, done: true }
除了for...of,还有哪些是使用iterator的变种(请注意,Object对象默认没有部署 Iterator 接口)
- 解构赋值:对数组和 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'];
- 扩展运算符:扩展运算符(...)也会调用默认的 Iterator 接口。
// 例一
var str = 'hello';
[...str] // ['h','e','l','l','o']
// 例二
let arr = ['b', 'c'];
['a', ...arr, 'd']
// ['a', 'b', 'c', 'd']
- yield:yield后面跟的是一个可遍历的结构,它会调用该结构的遍历器接口。
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 }
- 字符串对象也实现了 Iterator 接口(我的理解,字符串本质上就是一个char数组,所以它可以被遍历也是情理之中的了)
var someString = "hi";
var iterator = someString[Symbol.iterator]();
iterator.next() // { value: "h", done: false }
iterator.next() // { value: "i", done: false }
iterator.next() // { value: undefined, done: true }
使用 Generator 函数实现遍历接口
let myIterable = {
[Symbol.iterator]: function* () {
yield 1; // yield 是产出的意思
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"
其它遍历器对象:
let map = new Map().set('a', 1).set('b', 2);
for (let pair of map) {
console.log(pair);
}
// ['a', 1]
// ['b', 2]
像上面的例子,map应该调用了它默认的遍历生成器。如果我只想遍历map的key或者value怎么办呢?
答案是通过:entries(), keys(), values()
等。
ES6 的数组、Set、Map 都部署了以下三个方法,调用后都返回遍历器对象。
-
entries()
返回一个遍历器对象,用来遍历并返回[键名, 键值]组成的数组。对于数组,键名就是索引值;对于 Set,键名与键值相同。Map 结构的 Iterator 接口,默认就是调用entries方法。 -
keys()
返回一个遍历器对象,用来遍历所有的键名。 -
values()
返回一个遍历器对象,用来遍历所有的键值。
类数组对象举例:
let arrayLike = { length: 2, 0: 'a', 1: 'b' };//类数组对象
// 报错
for (let x of arrayLike) {
console.log(x);
}
// 正确,先使用Array.from将其转为带 Iterator 接口的数组
for (let x of Array.from(arrayLike)) {
console.log(x);
}
无法中途跳出forEach循环,break命令或return命令都不能奏效。
非重点:
遍历器对象的 return(),throw()
遍历器对象除了具有next方法,还可以具有return方法和throw方法。如果你自己写遍历器对象生成函数,那么next方法是必须部署的,return方法和throw方法是否部署是可选的。
return方法的使用场合是,如果for...of循环提前退出(通常是因为出错,或者有break语句),就会调用return方法。如果一个对象在完成遍历前,需要清理或释放资源,就可以部署return方法。
function readLinesSync(file) {
return {
[Symbol.iterator]() {
return {
next() {
return { done: false };
},
return() {
file.close();
return { done: true };
}
};
},
};
}
上面代码中,函数readLinesSync接受一个文件对象作为参数,返回一个遍历器对象,其中除了next方法,还部署了return方法。下面的两种情况,都会触发执行return方法。
// 情况一
for (let line of readLinesSync(fileName)) {
console.log(line);
break;
}
// 情况二
for (let line of readLinesSync(fileName)) {
console.log(line);
throw new Error();
}
上面代码中,情况一输出文件的第一行以后,就会执行return方法,关闭这个文件;情况二会在执行return方法关闭文件之后,再抛出错误。
注意,return方法必须返回一个对象,这是 Generator 规格决定的。
throw方法主要是配合 Generator 函数使用,一般的遍历器对象用不到这个方法。