先总结下es6常用的几种循环:
forEach,map,filter,some,every,indexOf,lastIndexOf,reduce,reduceRight
其中,粗体标记的是我目前认为会比较常用的方法。
特别声明,学习的文章以及部分内容摘抄自ImpulsionAndpower的博客文章:
https://blog.csdn.net/pupilxiaoming/article/details/78315419
以下是我对ImpulsionAndpower文章阅读后的理解:
forEach循环
forEach循环似乎以前就已经有过了,不过在es6中,应该出现了一些变化。
如文章所说最基础的示例是:
[1, 2 ,3, 4].forEach(console.log);
// 结果:
// 1, 0, [1, 2, 3, 4]
// 2, 1, [1, 2, 3, 4]
// 3, 2, [1, 2, 3, 4]
// 4, 3, [1, 2, 3, 4]
这是forEach最简单的示例,事实上,forEach的完全体应该是酱紫:
[].forEach(function(value, index, array) {
// ...
});
由上方方法的参数可以看出:
forEach方法中的function回调支持3个参数,第1个是遍历的数组内容;第2个是对应的数组索引,第3个是数组本身。
这里涉及到一个以前老是模糊理解的概念:回调函数
回调函数即是指:主函数运行完成后,再回过头去调用的函数,即使主函数在一开始就调用了回调函数,它也会在当前主体函数运行到最后时才进入回调函数内部。
关于forEach的例子,直接使用文章中的例子,就已经是最简单易懂的了。
var sum = 0;
[1, 2, 3, 4].forEach(function (item, index, array) {
sum += item;
});
alert(sum); // 10
forEach的进一步使用
原文
forEach除了接受一个必须的回调函数参数,还可以接受一个可选的上下文参数(改变回调函数里面的this指向)
array.forEach(callback,[ thisObject])
var database = {
users: ["张含韵", "江一燕", "李小璐"],
sendEmail: function (user) {
if (this.isValidUser(user)) {
console.log("你好," + user);
} else {
console.log("抱歉,"+ user +",你不是本家人");
}
},
isValidUser: function (user) {
return /^张/.test(user);
}
};
// 给每个人法邮件
database.users.forEach( // database.users中人遍历
database.sendEmail, // 发送邮件
database // 使用database代替上面标红的this
);
// 结果:
// 你好,张含韵
// 抱歉,江一燕,你不是本家人
// 抱歉,李小璐,你不是本家
如果这第2个可选参数不指定,则使用全局对象代替(在浏览器是为window),严格模式下甚至是undefined.
关于这个例子有两个需要注意的地方:
- 一开始提到的“forEach除了接受一个必须的回调函数参数”
- 还可以接受一个可选的上下文参数(改变回调函数里面的this指向)
这也是为什么一开始要明确理解回调函数的原因,且注意里面的“接受”被我加粗了。因为之前的简单的forEach是可以直接进行操作的,forEach的function自带有三个回调参数的,一个值,一个索引,一个原数组。而如果需要,我们还可以传入一个自写的回调函数。文中提到的“必须”,应该是指,如果要给forEach传入非默认的三个回调参数以外的东西,就必须以传入回调函数的方式进行。
关于上下文参数,我理解的是“作用域”。例子中可以看到,对于database下的users数组调用forEach,回调函数使用的是database下的sendEmail方法,因为该回调函数中使用到了database下的另一个方法isValidUser,而forEach的调用是在外面,即是说,作用域并不在database内部了,this的指向将是全局。第二个参数是database本身,代表的是改变作用域。那么回调函数启用时的this指向的就是database。
另外, forEach不会遍历纯粹“占着官位吃空饷”的元素的,例如下面这个例子:
var array = [1, 2, 3];
delete array[1]; // 移除 2
alert(array); // "1,,3"
alert(array.length); // but the length is still 3
array.forEach(alert); // 弹出的仅仅是1和3
这里其实存在一定疑问,当delete被删除后,数组被for循环遍历的话,索引1(即被delete的2)的位置打印出来的是undefined
然而我亲自尝试过,如果array直接赋值为[1,undefined,3]或者[1,null,3]或者[1,0,3]或者[1,false,3],forEach都是可以打印出来的。所以文章所指的“不会遍历纯粹‘占着官位吃空饷’的元素”,应该是指被delete的情况,不会进行判断。
forEach的总结
简单来说,我感觉forEach其实就是个简化的for循环,它依旧具有for循环的一切功能,基础来说,它的value、index、array可操作性可能更强。值得注意的是,forEach循环判断结束,是在遍历完成后undefined的情况才停止的。
通过这个验证可以发现,forEach打印出来的undefined,只是超过数组长度时,arr数组的索引为5的对象,为undefined。而哪怕值是undefined也可以放心打印出来。
map循环
Map跟forEach很相似
array.map(callback,[ thisObject]);
而默认的回调函数跟forEach也是一样的
[].map(function(value, index, array) {
// ...
});
区别在于,forEach只是直接遍历数组,而map是映射。即:会将原数组遍历,操作后映射到新数组里,相当于一次数组深复制。
值得注意的是,callback需要有return值:
var data = [1, 2, 3, 4];
var arrayOfSquares = data.map(function (item) {
return item * item;
});
alert(arrayOfSquares); // 1, 4, 9, 16
如果callback没有return值,所有值就会被映射成undefined:
var data = [1, 2, 3, 4];
var arrayOfSquares = data.map(function() {});
arrayOfSquares.forEach(console.log);
以下是我实验得到的数据截图
实际使用中,map方法可以直接使用到对象数组中的特指参数,使用方式有点类似于vue中的v-for,也是js中for循环的功能,这里直接引用的文章中的例子:
var users = [
{name: "张含韵", "email": "zhang@email.com"},
{name: "江一燕", "email": "jiang@email.com"},
{name: "李小璐", "email": "li@email.com"}
];
var emails = users.map(function (item) { return item.email; });
console.log(emails.join(", ")); // zhang@email.com, jiang@email.com, li@email.com
map总结
事实上,map跟forEach最大的区别就在于,forEach本身只是遍历数组,是在原数组基础上进行操作的。而map是将原数组操作后映射到一个新的数组。
filter循环
filter的主要作用在于“筛选”、“过滤”,最终也会和map一样,映射进一个新的数组里。区别是filter的return不是必须的
array.filter(callback,[ thisObject]);
它的callback同样需要有return值,不同之处在于,它return的时候是会判断一次的,只有return的true才会被返回,如果return的false则会被遗弃。
原文示例:
var data = [0, 1, 2, 3];
var arrayFilter = data.filter(function(item) {
return item;
});
console.log(arrayFilter); // [1, 2, 3]
我自写的示例
值得一提的是,返回值只要是弱等于== true/false就可以了,而非非得返回 === true/false.
filter总结
在某些需要遍历数组,且有判断的时候,filter就特别有用,它的判断机制甚至可以写成一个函数进行判断,相比之下就比原来的for循环节约很多效率。
实验如下:
1:利用callback进行简单的筛选
2:利用callback进行多级筛选
由此可见,filter本身的return自己就是一个if的判断,可以节约一步判断逻辑的书写。
some循环
some的意义是“某些”,只要某些项满足即会返回true,在我看来,很大程度是,它是类似indexOf的。同样的,它的用法类似于forEach
array.some(callback,[ thisObject]);
以下是原文中的例子
var scores = [5, 8, 3, 10];
var current = 7;
function higherThanCurrent(score) {
return score > current;
}
if (scores.some(higherThanCurrent)) {
alert("朕准了!");
}
文中例子声明了一个数组,里面是无序的四个数字,然后声明一个current为数字7。最后利用callback进行some的遍历,当发现数组中某一个数字大于7时,循环便会停止,返回true,否则会返回false,这个过程非常类似indexOf,只是indexOf返回的是数组下标。
some总结
some可以用于简单的判断搜寻,主要用于判断“是否存在”,而不能判断“在哪儿”。使用局限性比较大。不过如果只是查找是否存在,是很简便的方法。
every循环
every循环类似some循环,按原文的意思,是一对好基友,事实上它的作用就在于,数组中必须每一项都为true才会返回true,否则会返回false。而不是some那样,一旦遇到true即停止循环返回true。
例子就不举了,跟some几乎一样。
every总结
雷同some的使用方式,区别于indexOf,更偏向于整体判断数组。应用面同样不大,但应用场景其实很多。也是值得使用的方法之一。
reduce循环
如原文所说,我比较赞同reduce更偏向于“迭代”而不是“减少”或则“简约”的意思。它的主要作用就是前后数据的操作,个人感觉通常用于简单的统计算法,例如求和之类的地方。
对于reduce的理解,配合例子比较好理解点,以下是原文的介绍和例子:
callback函数接受4个参数:之前值、当前值、索引值以及数组本身。initialValue参数可选,表示初始值。若指定,则当作最初使用的previous值;如果缺省,则使用数组的第一个元素作为previous初始值,同时current往后排一位,相比有initialValue值少一次迭代。
var sum = [1, 2, 3, 4].reduce(function (previous, current, index, array) {
return previous + current;
});
console.log(sum); // 10
一眼可以看出来,1+2=3,3+3=6,6+4=10,所以最后sum=10。reduce的作用不严谨地直观理解就可以是:
前一个值(对象)跟下一个值(对象)进行操作,得到的值(对象)再跟下一个值(对象)进行操作。
以下是原文中的说明:
由于原文并没有解释initialValue这个可选参数该怎么使用,于是查阅了下文档,并进行试验,同样的例子:
由此可见,initialValue是写在最后的而不是作为参数写在方法体内的。这是需要注意的地方。
另外,原文中对于二维数组的扁平化也有介绍:
var matrix = [
[1, 2],
[3, 4],
[5, 6]
];
// 二维数组扁平化
var flatten = matrix.reduce(function (previous, current) {
return previous.concat(current);
});
console.log(flatten); // [1, 2, 3, 4, 5, 6]
例子中只是单纯的数组拼接,事实上如果三个数组内的对象都为键值对,结合map应该会有更好的操作方式,可惜在试验过程中失败了。遇到的主要问题在于:
- reduce循环最后会undefined一次,这时候map的调用会报错。
- reduce循环时,各自可以map出previous和current的子元素(因为是二维数组,所以previous和current自身也是个数组),然而暂时没想到好的办法进行操作和拼接。本次学习重点是基础运用,暂时就不深入探究了。
贴出失败的代码,它是由上面的例子改变的,希望不使用concat拼接的情况下,就能够将字符串拼接在一起。然而就遇到了如上两个问题,以下是实验使用到的被终止的代码:
var matrix = [
[{name:'a'}, {name:'b'}],
[{name:'c'}, {name:'d'}],
[{name:'e'}, {name:'f'}],
];
// 二维数组扁平化
var flatten = matrix.reduce(function (previous, current){
let previousName = previous.map((item)=>{
return item.name + item.name
})
let currentName = current.map((item)=>{
return item.name + item.name
})
console.log(previousName,'previousName')
console.log(currentName,'currentName')
// return previousName + currentName
});
// console.log(flatten);
以下是打印出的内容,进行到这里就中断了试验,等以后有更好的例子或者感悟时,会回来进行补充和修改(暂时的模糊想法是reduce的嵌套。不过没有太大必要进行试验,不是本次学习的重点)
reduce总结
reduce更偏向于前后数据的对比统计操作,类似“求和”之类的操作最是方便。如我试验中的情况,或许会用到reduce的嵌套,相比之下的实用度和性价比有待商榷。不可否认的是,一维数组简单对比统计,reduce是特别方便的。
学习总结
indexOf、lastIndexOf以及reduceRight我没有去写,indexOf是曾经就掌握的东西,也是比较简单的东西,就没有去赘述,lastIndexOf是反向使用的indexOf。而reduceRight是反向的reduce,也就没有详细写了。
其实以上方法对于一维数组是最高效的,多维数组或复杂的逻辑判断,只用单一某一种循环进行判断,我认为是性价比较低的,应该组合使用,大可以使用map将原始数据映射成简单的一维数组,再由reduce进行统计计算。最后甚至可以使用some或者every进行判断之类的,我想效率会高很多。
es6循环的学习暂时就告一段落,更多的是需要在实际生产学习中多使用才能有更多的感悟和熟练度。加油~