本文是ES2015系列的第六篇文章,分享一下我对于es6中迭代器的一些认识。迭代器和生成器作为两个重要的es6特性,我会分两篇文章讨论,本文属于个人的学习总结,如果有任何写的不合理的地方,希望大家多多指正。
一. 迭代器的定义与使用场景
wiki百科中对于迭代器给出的解释是:在计算机程序中,一个迭代器一般指的是可以使你遍历整个容器的对象,这个容器可以是一个数组,也可以是一个列表结构,迭代器每次从容器中取出一个值,并移动其“指针”,使得下次取值的时候从上一个值结束的位置开始提取,直到取出所有值后结束整个迭代。迭代器在很多语言中比如java,c#,python中迭代器都是原生支持的。es6通过定义了标准的迭代器接口,使其支持迭代行为。
迭代器既然具有这样的行为,那么我们就可以使用迭代器去做很多事情比如:
- 迭代产生一个UUID(独一无二的ID值)
- 从一个数据集合中依次循环输出每一个数据
- 数据库查询返回结果,每次取出其中的一条数据
只要实施了迭代器的接口我们就可以给任何我们的数据集合增加迭代行为。接下来我们就看下如何定义迭代器。
二. 迭代器的声明与定义
2.1. 迭代器协议(iterator protocol)
在es6中定义的迭代器接口,实际上定义了迭代器协议的内容,也就是定义了迭代器对象的结构,一个迭代器必须具有next()方法,该方法定义了必须返回一个标准的迭代结果(IteratorResult),而且该返回结果拥有自己的结构,如下面所示
方法:next(){ return IteratorResult }
返回类型:IteratorResult:
{
done : true/false, // 当true时,结束迭代行为。
value :any_javascript_object //可以使任何对象内容,当done为true时,值被忽略
}
只要具有上述接口的我们称之为支持迭代器协议,下面的实例来自于MDN,通过定义一个makeIterator函数将数组转换为迭代器,makeIterator函数执行返回结果就是一个具有next方法属性的迭代器对象,通过闭包,维持一个索引指针数据:
function makeIterator(array){
var nextIndex = 0;
return {
next: function(){
return nextIndex < array.length ?
{value: array[nextIndex++], done: false} :
{done: true};
}
};
}
var it = makeIterator(['yo', 'ya']);
console.log(it.next().value); // 'yo'
console.log(it.next().value); // 'ya'
console.log(it.next().done); // true
上面代码中档指针超出数组长度之后,返回的迭代结果中done设置为了true,值value变为undefined。
2.2 可迭代协议(iterable protocol)
可迭代协议指的是实施了@@iterator方法的对象,也就是必须拥有一个属性为[Symbol.iterator]的方法,这是一个不带参数的函数,返回值必须是一个迭代器。对于内置的对象String, Array, TypedArray, Map 和 Set 都支持可迭代协议,也就是说我们可以通过访问这些对象的[Symbol.iterator]获得一个可迭代的对象,这些类型也原生支持for…of循环输出迭代的结果。
var arr=[1,2,3]
var it=arr[Symbol.iterator]();
for(let i=0;i<4;i++){
console.log(it.next())
}
//Output
//{"value":1,"done":false}
//{"value":2,"done":false}
//{"value":3,"done":false}
//{"done":true}
var mapObj=new Map()
mapObj.set("name","mike");
mapObj.set(["country","city"],["China","Beijing"])
var it=mapObj[Symbol.iterator]();
for(let i=0;i<3;i++){
console.log(it.next());
}
===========================OUTPUT=============================
{"value":["name","mike"],"done":false}
{"value":[["country","city"],["China","Beijing"]],"done":false}
{"done":true}
2.3. 结合两种协议实现一个简单的迭代器
既然我们知道了迭代器的两种协议,那么我们就可以定义一个迭代器,使其[Symbol.iterator]返回对象this本身,成为一个可迭代的迭代器。
var it={
index:1,
next(){
return {
value:this.index++,
done:this.index>10?true:false
}
},
[Symbol.iterator](){
return this;
}
}
for (var i of it){
console.log(i)
}
===========================OUTPUT=============================
1 2 3 4...9
for…of循环将执行迭代器的next()方法直到其中的done值为true,结束迭代。
三. 迭代器应用实例
3.1 斐波那契数列
下面我们就使用迭代器来自定义自己的一个斐波那契数列组,我们直到斐波那契数列有两个运行前提,第一个前提是初始化的前两个数字为0,1,第二个前提是将来的每一个值都是前两个值的和。这样我们的目标就是每次都迭代输出一个新的值。
var it={
[Symbol.iterator](){
return this
},
n1:0,
n2:1,
next(){
let temp1=this.n1,temp2=this.n2;
[this.n1,this.n2]=[temp2,temp1+temp2]
return {
value:temp1,
done:false
}
}
}
for (var i=0;i<20;i++){
console.log(it.next())
}
===========================OUTPUT=============================
{"value":0,"done":false}
{"value":1,"done":false}
{"value":1,"done":false}
{"value":2,"done":false}
{"value":3,"done":false}
{"value":5,"done":false}
...
{"value":2584,"done":false}
{"value":4181,"done":false}
3.2 任务队列迭代器
我们可以定义一个任务队列,该队列初始化时为空,我们将待处理的任务传递后,传入数据进行处理。这样第一次传递的数据只会被任务1处理,第二次传递的只会被任务2处理… 代码如下:
var Task={
actions:[],
[Symbol.iterator](){
var steps=this.actions.slice();
return {
[Symbol.iterator](){
return this;
},
next(...args){
if(steps.length>0){
let res=steps.shift()(...args);
return {
value:res,
done:false
}
}else{
return {done:true}
}
}
}
}
}
Task.actions.push(function task1(...args){
console.log("任务一:相乘")
return args.reduce(function(x,y){return x*y})
},function task2(...args){
console.log("任务二:相加")
return args.reduce(function(x,y){return x+y})*2
},function task3(...args){
console.log("任务三:相减")
return args.reduce(function(x,y){return x-y})
});
var it=Task[Symbol.iterator]();
console.log(it.next(10,100,2));
console.log(it.next(20,50,100))
console.log(it.next(10,2,1))
===========================OUTPUT=============================
任务一:相乘
{"value":2000,"done":false}
任务二:相加
{"value":340,"done":false}
任务三:相减
{"value":7,"done":false}
3.3 延迟执行
假设我们有一个数据表,我们想按大小顺序依次的获取数据,但是我们又不想提前给他排序,有可能我们根本就不去使用它,所以我们可以在第一次使用的时候再排序,做到延迟执行代码。
var table={"d":1,"b":4,"c":12,"a":12}
table[Symbol.iterator] = function () {
var _this = this;
var keys = null;
var index = 0;
return {
next: function () {
if (keys === null) {
keys = Object.keys(_this).sort();
}
return {
value: keys[index], done: index++>keys.length
};
}
}
}
for(var a of table){
console.log(a)
}
===========================OUTPUT=============================
a b c d
四. 总结
本文主要是介绍了两种迭代器的协议,以及如何使用迭代器,简单的介绍了几个迭代器的使用实例。下一节内容我们介绍一些生成器的内容,欢迎大家参与讨论。