# iterator 迭代器
很多内置对象实现Iterable接口:
- String
- Array
- Map
- Set
- arguments
- NodeList等Dom集合类型
ES6 制订了一套标准化的迭代器接口(包含 3 个方法,见下表),只要实现了这套接口都能成为迭代器。
| 方法 | 返回值 | 描述 |
|---|---|---|
| next() | IteratorResult | 必选,获取下一个迭代器结果 |
| return() | IteratorResult | 可选,停止迭代并返回一个迭代器结果 |
| throw() | IteratorResult | 可选,抛出错误并返回一个迭代器结果 |
检查是否存在默认存在迭代器属性可以暴露[Symbol.iterator]这个工厂函数,可迭代对象:包含 Symbol.iterator 属性的对象被称为可迭代对象(Iterable),
Symbol.iterator 是一个特殊的内置符号,它的值是一个返回迭代器的方法。
let num = 1;
let obj = {};
console.log(num[Symbol.iterator]); // undefined
console.log(obj[Symbol.iterator]); // undefined
let str = 'abc';
let arr = ['a', 'b', 'c'];
let els = document.querySelectorAll('div');
// These types all have iterator factories
console.log(str[Symbol.iterator]); // f values() { [native code] }
console.log(set[Symbol.iterator]); // f values() { [native code] }
console.log(els[Symbol.iterator]); // f values() { [native code] }
// Invoking the factory function produces an Iterator
console.log(str[Symbol.iterator]()); // StringIterator {}
console.log(set[Symbol.iterator]()); // SetIterator {}
console.log(els[Symbol.iterator]()); // ArrayIterator {}
下面的示例使用了数组的默认迭代,下面的示例使用了数组的默认迭代.
var arr = ["a", "b"],
iterator = arr[Symbol.iterator]();
iterator.next(); //{value: "a", done: false}
iterator.next(); //{value: "b", done: false}
iterator.next(); //{value: undefined, done: true}
具有迭代器接口的数据,可以使用for...of/扩展运算符/Array.from/转Map/转Set等操作
let s ='abcde'
let f1=[...s]
console.log(f1)//[a,b,c,d,e]
let f2 = Array.from(s)
console.log(f2)//[a,b,c,d,e]
let f3 =new Set(s)
console.log(f3)//Set(5) {'a', 'b', 'c', 'd', 'e'}
let tems = f1.map((el,i)=>[el,i])
let f4 =new Map(tems)
console.log(f4)//Map(5) {'a' => 0, 'b' => 1, 'c' => 2, 'd' => 3, 'e' => 4}
# 迭代器(Iterator)实现原理
for...of实现的前提是一个数据结构部署了 Symbol.iterator 属性
再看一个普通的字面量对象例子:
var obj={0:"rrr",1:"444"};
for(var value of obj){
console.log(value);
}
运行报错,表明这个字面量对象是不可遍历的。那能否像Array,String等对象一样支持for-of遍历呢?答案是可以的,Array,String等对象之所以可遍历,是因为在它们的原型上,实现了迭代器( Iterator )的接口。
- 那如何实现迭代器(Iterator)接口呢,只需要在这个对象中实现[Symbol.iterator]属性方法.
- 最重要的是要实现next方法!!!next方法中要实现返回值{value:xx,done:xxx}
var obj={
0:"rrr",
1:"444",
//实现[Symbol.iterator]属性方法
[Symbol.iterator]:function(){
const self = this;
let index=0;
return {
next:function(){//实现next
if(index<2){
return {//遍历中
value:self[index++],
done: false//表示遍历没有结束,done设置为fasle
}
}else{
return{//遍历结束
value:undefined,//结束后,返回undefined
done: true//done设置为true,表示遍历结束
}
}
}
}
}
};
for(var value of obj){
console.log(value);//"rrr","444"
}
正确的遍历了obj,打印了obj的属性的值。再来看下[Symbol.iterator]属性函数体,返回了next方法,next方法中遍历了此obj的属性对象,并返回字面量对象{value:xxx,done:xxx}这种结构对象,在遍历过程中,value返回对应属性值,且done设置为false;当遍历结束后,value返回undefined,且done设置为true。
var it = obj[Symbol.iterator]();
console.log(it.next());//{value: "rrr", done: false}
console.log(it.next());//{value: "444", done: false}
console.log(it.next());//{value: undefined, done: true}
这就是for-of的原理,每次遍历都会调用该对象的[Symbol.iterator]属性的next方法,当返回{value: undefined, done: true}后,表示遍历结束。
所以任何对象要变成可遍历的对象,只需要实现[Symbol.iterator]属性方法,定义其中的next方法即可。
# 创建可迭代对象
下面用ES5的语法创建一个迭代器
function createIterator(items) {
var i = 0;
return {
next: function() {
var done = (i >= items.length);
var value = !done ? items[i++] : undefined;
return {
done: done,
value: value
};
}
};
}
var iterator = createIterator([1, 2, 3]);
console.log(iterator.next()); // "{ value: 1, done: false }"
console.log(iterator.next()); // "{ value: 2, done: false }"
console.log(iterator.next()); // "{ value: 3, done: false }"
console.log(iterator.next()); // "{ value: undefined, done: true }"
// 之后的所有调用
console.log(iterator.next()); // "{ value: undefined, done: true }"
在上面这段代码中,createIterator()方法返回的对象有一个next()方法,每次调用时,items数组的下一个值会作为value返回。当i为3时,done变为true;此时三元表达式会将value的值设置为undefined。最后两次调用的结果与ES6迭代器的最终返回机制类似,当数据集被用尽后会返回最终的内容
# es6模式创建迭代器
普通的对象只要包含 Symbol.iterator 属性,并返回一个迭代器,就能摇身一变成为可迭代对象。在下面的代码中,iterable 对象本身就是迭代器,因此Symbol.iterator 属性的返回值可以是当前对象。注意,必须返回迭代器,否则会出现不可预料的错误。
var iterable = {
items: ["a", "b"],
index: 0,
[Symbol.iterator]() {
return this;
},
next() {
var done = this.index >= this.items.length;
return {
value: this.items[this.index++],
done: done
};
},
return () {
return {
value: undefined,
done: true
};
}
};
for (var value of iterable) {
console.warn(value)
}
// a
// b
在 iterable 对象中,包含 return()方法,只有当迭代器终止遍历时,才会触发此方法。例如,在 for-of 循环体中执行 break、continue 或 return 语句,甚至还能通过调用 throw 语句抛出自定义异常,提前结束代码的运行,以此实现触发条件,如下所示。
for (var value of iterable) {
throw new Error();
}
# 迭代器实例不互相干扰
let arr = ['foo', 'bar'];
let iter1 = arr[Symbol.iterator]();
let iter2 = arr[Symbol.iterator]();
console.log(iter1.next()); // { done: false, value: 'foo' }
console.log(iter2.next()); // { done: false, value: 'foo' }
console.log(iter2.next()); // { done: false, value: 'bar' }
console.log(iter1.next()); // { done: false, value: 'bar' }
- 迭代器并不与可迭代对象某个时刻的快照绑定,而仅仅是使用游标来记录遍历可迭代对象的历程,如果期间改变了对象,迭代器也会反映相应的变化
let arr = ['foo', 'baz'];
let iter = arr[Symbol.iterator]();
console.log(iter.next()); // { done: false, value: 'foo' }
// Insert value in the middle of array
arr.splice(1, 0, 'bar');
console.log(iter.next()); // { done: false, value: 'bar' }
console.log(iter.next()); // { done: false, value: 'baz' }
console.log(iter.next()); // { done: true, value: undefined }
# 提前终止迭代器
- 当使用break或者抛出异常的时候,如果迭代器内部实现了return方法,则会执行提前终止迭代器
class Counter {
constructor(limit) {
this.limit = limit;
}
[Symbol.iterator]() {
let count = 1,
limit = this.limit;
return {
next() {
if (count <= limit) {
return { done: false, value: count++ };
} else {
return { done: true };
}
},
return() {
console.log('Exiting early');
return { done: true };
}
};
}
}
let counter1 = new Counter(5);
for (let i of counter1) {
if (i > 2) {
// break; //注释掉后 1 2 3 4 5
}
console.log(i);
}
// 1
// 2
// Exiting early
let counter2 = new Counter(5);
try {
for (let i of counter2 ){
if (i > 2) {
throw 'err';
}
console.log(i);
}
} catch(e) {}
// 1
// 2
// Exiting early
let counter3 = new Counter(5);
let [a, b] = counter3;
// Exiting early
- 如果迭代器没有关闭,则会继续从上次离开的地方继续迭代,比如数组的迭代器就不能关闭:
let a = [1, 2, 3, 4, 5];
let iter = a[Symbol.iterator]();
for (let i of iter) {
console.log(i);
if (i > 2) {
break
}
}
// 1
// 2
// 3
for (let i of iter) {
console.log(i);
}
// 4
// 5
迭代器中return不一定存在,也就是并非所有的迭代器都是可关闭的,可以通过判断是否有return方法来判断是否是可关闭的迭代器对象。给不可关闭的迭代器增加一个return方法并不能让它变可关闭,这是因为调用return不会强制迭代器进入关闭状态,即便如此,return方法还是会执行。
let a = [1, 2, 3, 4, 5];
let iter = a[Symbol.iterator]();
iter.return = function(){
console.log('退出')
return {done:true}
}
for (let i of iter) {
console.log(i);
if (i > 2) {
break
}
}
// 1
// 2
// 3
//退出
for (let i of iter) {
console.log(i);
}
// 4
// 5
← promise generator函数 →