ES6 迭代器(Iterator)和 for.of循环使用方法学习(总结)
一、什么是迭代器?
生成器概念在Java,Python等语言中都是具备的,ES6也添加到了JavaScript中。Iterator可以使我们不需要初始化集合,以及索引的变量,而是使用迭代器对象的next方法,返回集合的下一项的值,偏向程序化。
迭代器是带有特殊接口的对象。含有一个next()方法,调用返回一个包含两个属性的对象,分别是value和done,value表示当前位置的值,done表示是否迭代完,当为true的时候,调用next就无效了。
ES5中遍历集合通常都是for循环,数组还有forEach方法,对象就是for-in,ES6中又添加了Map和Set,而迭代器可以统一处理所有集合数据的方法。迭代器是一个接口,只要你这个数据结构暴露了一个iterator的接口,那就可以完成迭代。ES6创造了一种新的遍历命令for...of循环,Iterator接口主要供for...of消费。
二、如何使用迭代器?
1、默认Iterator接口
数据结构只要部署了Iterator接口,我们就成这种数据结构为“可遍历”(Iterable)。ES6规定,默认的Iterator接口部署在数据结构的Symbol.iterator属性,或者说,一个数据结构只要具有Symbol.iterator数据,就可以认为是“可遍历的”(iterable)。
可以供for...of消费的原生数据结构
- Array
- Map
- Set
- String
- TypedArray(一种通用的固定长度缓冲区类型,允许读取缓冲区中的二进制数据)
- 函数中的arguments对象
- NodeList对象
可以看上面的原生数据结构中并没有对象(Object),为什么呢?
那是因为对象属性的遍历先后顺序是不确定的,需要开发者手动指定。本质上,遍历器是一种线性处理,对于任何非线性的数据结构,部署遍历器接口就等于部署一种线性变换。
做如下处理,可以使对象供for...of消费:
//code1
functionObj(value){
this.value=value;
this.next=null;
}
Obj.prototype[Symbol.iterator]=function(){
variterator={
next:next
};
varcurrent=this;
functionnext(){
if(current){
varvalue=current.value;
current=current.next;
return{
done:false,
value:value
};
}else{
return{
done:true
};
}
}
returniterator;
}
varone=newObj(1);
vartwo=newObj(2);
varthree=newObj(3);
one.next=two;
two.next=three;
for(variofone){
console.log(i);
}
//1
//2
//3
2、调用Iterator接口的场合
(1)解构赋值
//code2
letset=newSet().add('a').add('b').add('c');
let[x,y]=set;
//x='a';y='b'
let[first,...rest]=set;
//first='a';rest=['b','c'];
(2)扩展运算符
//code3 //例一 varstr='hello'; [...str]//['h','e','l','l','o'] //例二 letarr=['b','c']; ['a',...arr,'d'] //['a','b','c','d']
(3)Generator函数中的yield*表达式(下一章介绍)
//code4
letgenerator=function*(){
yield1;
yield*[2,3,4];
yield5;
};
variterator=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}
(4)其它场合
- for..of
- Array.from
- Map()、Set()、WeakMap()、WeakSet()
- Promise.all()
- Promise.race()
3、for...of循环的优势
先看看,数组forEach方法的缺点:
//code5
myArray.forEach(function(value){
console.log(value);
});
这个写法的问题在于,无法中途跳出forEach循环,break命令或return命令都不能生效。
再看看,对象for...in的循环的缺点:
for(varindexinmyArray){
console.log(myArray[index]);
};
- 数组的键名是数字,但是for...in循环是以字符串作为键名,“0”、“1”、“2”等。
- for...in循环不仅可以遍历数字键名,还会遍历手动添加的期推荐,甚至包括原型链上的键。
- 某些情况下,for...in循环会议任意顺序遍历键名
- for...in遍历主要是为遍历对象而设计的,不适用于遍历数组
那么,for...of有哪些显著的优点呢?
- 有着同for...in一样的简洁语法,但是没有for...in那些缺点
- 不同于forEach方法,它可以与break、continue和return配合使用
- 提供了遍历所有数据结构的统一操作接口
for(varnoffibonacci){
if(n>1000){
break;
console.log(n);
}
}
4、各数据类型如何使用for...of循环?
(1)数组
for...of循环允许遍历数组获得键值
vararr=['a','b','c','d'];
for(letainarr){
console.log(a);//0123
}
for(letaofarr){
console.log(a);//abcd
}
for...of循环调用遍历器接口,数组的遍历器接口只返回具有数字索引的值
letarr=[3,5,7];
arr.foo='hello';
for(letiinarr){
console.log(i);//"0","1","2","foo"
}
for(letiofarr){
console.log(i);//"3","5","7"
}
(2)Map和Set结构
varengines=newSet(["Gecko","Trident","Webkit","Webkit"]);
for(vareofengines){
console.log(e);
}
//Gecko
//Trident
//Webkit
vares6=newMap();
es6.set("edition",6);
es6.set("committee","TC39");
es6.set("standard","ECMA-262");
for(var[name,value]ofes6){
console.log(name+":"+value);
}
//edition:6
//committee:TC39
//standard:ECMA-262
由上述的代码可以看出,for...of循环遍历Map和Set结构时,遍历的顺序是按照各个成员被添加进数据结构的顺序,Set结构遍历时返回的是一个值,而Map结构遍历时返回的是一个数组,该数组的两个成员分别为当前Map成员的键名和键值。
(3)类数组对象
字符串
//普通的字符串遍历
letstr="yuan";
for(letsofstr){
console.log(s);//yuan
}
//遍历含有32位utf-16字符的字符串
for(letxof'a\uD83D\uDC0A'){
console.log(x);
}
//'a'
//'\uD83D\uDC0A'
DOMNodeList对象
letparas=document.querySelectorAll("p");
for(letpofparas){
p.classList.add("test");
}
arguments对象
functionprintArgs(){
for(letxofarguments){
console.log(x);
}
}
printArgs("a","n");
//"a"
//"n"
没有Iterator接口类数组对象的遍历处理
借用Array.from方法处理
letarrayLike={
length:2,
0:'a',
1:'b'
};
//报错
for(letxofarrayLike){
console.log(x);
}
//正确
for(letxofArray.from(arrayLike)){
console.log(x);
}
(4)对象
对于普通对象,不能直接使用for...of遍历,否则会报错,必须部署了Iterator接口才能使用。如下两种方法部署:
//方法一:使用Object.keys方法讲对象的键名生成一个数组
for(varkeyofObject.keys(someObject)){
console.log(key+":"+someObject[key]);
}
//方法二:使用Generator函数将对象重新包装一下
function*entries(obj){
for(letkeyofObject.keys(obj)){
yield[key,obj[key]];
}
}
for(let[key,value]ofentries(obj)){
console.log(key,"->",value);
}
//a->1
//b->2
//c->3
三、迭代器应用实例
1、斐波那契数列
下面我们就使用迭代器来自定义自己的一个斐波那契数列组,我们直到斐波那契数列有两个运行前提,第一个前提是初始化的前两个数字为0,1,第二个前提是将来的每一个值都是前两个值的和。这样我们的目标就是每次都迭代输出一个新的值。
varit={[Symbol.iterator](){
returnthis
},
n1:0,
n2:1,
next(){
lettemp1=this.n1,
temp2=this.n2;
[this.n1,this.n2]=[temp2,temp1+temp2]
return{
value:temp1,
done:false
}
}
}
for(vari=0;i<20;i++){
console.log(it.next())
}
//
"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
}
2、任务队列迭代器
我们可以定义一个任务队列,该队列初始化时为空,我们将待处理的任务传递后,传入数据进行处理。这样第一次传递的数据只会被任务1处理,第二次传递的只会被任务2处理…代码如下:
varTask={
actions:[],
[Symbol.iterator](){
varsteps=this.actions.slice();
return{[Symbol.iterator](){
returnthis;
},
next(...args){
if(steps.length>0){
letres=steps.shift()(...args);
return{
value:res,
done:false
}
}else{
return{
done:true
}
}
}
}
}
}
Task.actions.push(functiontask1(...args){
console.log("任务一:相乘")returnargs.reduce(function(x,y){
returnx*y
})
},
functiontask2(...args){
console.log("任务二:相加")returnargs.reduce(function(x,y){
returnx+y
})*2
},
functiontask3(...args){
console.log("任务三:相减")returnargs.reduce(function(x,y){
returnx-y
})
});
varit=Task[Symbol.iterator]();
console.log(it.next(10,100,2));
console.log(it.next(20,50,100))console.log(it.next(10,2,1))
//
任务一:相乘{
"value":2000,
"done":false
}任务二:相加{
"value":340,
"done":false
}任务三:相减{
"value":7,
"done":false
}
3、延迟执行
假设我们有一个数据表,我们想按大小顺序依次的获取数据,但是我们又不想提前给他排序,有可能我们根本就不去使用它,所以我们可以在第一次使用的时候再排序,做到延迟执行代码:
vartable={
"d":1,
"b":4,
"c":12,
"a":12
}
table[Symbol.iterator]=function(){
var_this=this;
varkeys=null;
varindex=0;
return{
next:function(){
if(keys===null){
keys=Object.keys(_this).sort();
}
return{
value:keys[index],
done:index++>keys.length
};
}
}
}
for(varaoftable){
console.log(a)
}
//abcd
四、结语
本章内容,重点是明白Iterator接口的机制,以及for...of循环的使用方法。下一章介绍生成器函数Generator函数。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。