Iterator 和 for...of 循环
参考:http://es6.ruanyifeng.com/#docs/generator
原生具备Iterator接口的数据结构
Array
Map
Set
String
TypedArray
函数的arguments对象
NodeList对象
一个对象如果要具备可被for...of循环调用的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};
}
return {done: true, value: undefined};
}
}
function range(start, stop) {
return new RangeIterator(start, stop);
}
for (var value of range(0, 3)) {
console.log(value); // 0, 1, 2
}
默认调用Iterator的场合
1.解构赋值
对数组和Set结构进行解构赋值时,会默认调用
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'];
2.扩展运算符
扩展运算符(...)会调用默认的 Iterator 接口
// 例一
var str = 'hello';
[...str] // ['h','e','l','l','o']
// 例二
let arr = ['b', 'c'];
['a', ...arr, 'd']
// ['a', 'b', 'c', 'd']
3.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接口
字符串是一个类似数组的对象,也原生具有 Iterator 接口
var someString = "hi";
typeof someString[Symbol.iterator]
// "function"
var iterator = someString[Symbol.iterator]();
iterator.next() // { value: "h", done: false }
iterator.next() // { value: "i", done: false }
iterator.next() // { value: undefined, done: true }
可以覆盖原生的Symbol.iterator方法,达到修改遍历器行为的目的
var str = new String("hi");
[...str] // ["h", "i"]
str[Symbol.iterator] = function() {
return {
next: function() {
if (this._first) {
this._first = false;
return { value: "bye", done: false };
} else {
return { done: true };
}
},
_first: true
};
};
[...str] // ["bye"]
str // "hi"
遍历器对象的return(),throw()
遍历器对象除了具有next方法,还可以具有return方法和throw方法。
return(),throw()方法可选
如果for...of循环提前退出(出错或有break语句),就会调用return方法
如果一个对象在完成遍历前,需要清理或释放资源,就可以部署return方法
function readLinesSync(file) {
return {
[Symbol.iterator]() {
return {
next() {
return {done:false};
},
return() {
file.close();
return {done:true};
}
}
}
}
}
触发return:
//情况1
for(let line of readLinesSync(filename)) {
console.log(line);
break;
}
//情况2
for(let line of readLinesSync(filename)) {
console.log(line);
throw new Error();
}
for...of循环
内部调用的是数据结构的Symbol.iterator方法
使用范围:数组,Set和Map结构,类似数组对象(arguments对象,DOM NodeList对象),Generator对象,以及字符串for...in循环获取对象的键名
for...of循环获取对象的键值
var arr = ['red', 'green', 'blue'];
for(let a in arr) {
console.log(a); //0, 1, 2, 3
}
for(let a of arr) {
console.log(a); //a, b, c, d
}
并不是所有类似数组的对象都具有Iterator接口
一个简单的方法就是使用Array.from方法将其转为数组
let arrayLike = { length:2, 0: 'a', 1: 'b'};
for (let x of Array.from(arrayLike)) {
console.log(x);
}
对象
普通的对象可以用for...in循环遍历键名
不能用for...of
可以使用Object.keys将对象的键名生成一个数组,然后遍历数组
for (var key of Object.keys(someObject)) {
console.log(key + ':' + someObject[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);
}
// a -> 1
// b -> 2
// c -> 3
与其他遍历语法的比较
for循环:比较麻烦
forEach方法:数组提供的内置方法,无法中途跳出forEach循环,break,return都不行
for...in循环:
缺点:
1.数组的键名是数字,但循环出来时字符串
2.不仅遍历数字键名,还会遍历手动添加的其他键,甚至包括原型链上的键
3.某些情况下,会以任意顺序遍历键名