今天我们来学习一个知识点:高阶函数。我们不要被高阶函数这个名词吓唬到了,高阶函数不过就是一个定义的函数的概念。我们只需要知道高阶函数代表什么含义就ok了。
我们需要知道的是,在JavaScript中,函数的一等公民。可见函数特别特别重要。因为在JavaScript中,函数特别的灵活。
1、函数既可以作为一个变量值;
2、函数又可以作为函数的参数;
3、函数还可以作为函数的返回值。
而满足第二点和第三点的函数,就是高阶函数。
什么是高阶函数?
当函数作为参数或者函数作为返回值的时候,那么就是高阶函数。
# 1、函数作为'参数'
# 2、函数作为'返回值'
一、函数作为参数
那么为什么需要让函数作为参数?
为了方便理解,我们不妨举一个栗子。
我们每天都要吃晚饭。吃完晚饭之后,我们去做什么事情就非常多选择了。你可以打游戏,你可以学习,你可以去跳广场舞。你可以去浪等等等等。每一天的饭后活动都可以不一样。
# 代码编写
那么我们如何通过代码的方式来模拟上面这种场景呢?
我们可以将吃晚饭定义函数。
接着模拟一个吃晚饭的动作。假设我们吃两秒钟。
我们调用吃饭的方法挺简单的。如下所示:
我们打印出来看一下,这就算吃完晚饭了。
那么接下来,我们并不知道吃完晚饭之后,要做什么事情是不确定的。
既然是不清楚吃晚饭以后该做些什么,因此是不固定的,不固定的话,我们就不能直接将事件写死在代码中。我们应该将,饭后的活动暴露出去,让调用者自己去决定做些什么?
概念理解起来很简单,但是应该如何体现在代码中呢?
这个就需要我们用到**“函数作为参数”**的知识点了。具体操作如下:
然后我们定义吃完饭后的事件。并不需要直接写死,而是使用代指的形式编写。
这样就算已经编写好了,我们并没有直接将饭后的活动直接写死。将具体的活动暴露出去了。
接下来,我们开始编写具体饭后的活动。假设我们饭后去学习。那么我们该怎么做呢?
具体的编码如下。
其实这样就算编写完成了,我们打印出看一下。
# 可以看出,出完晚饭之后,就去学习。
其实上面的代码就算编写完成了,最终的代码如下:
# 执行过程
如果说我们想要看上面的代码如何工作,也就是代码执行的过程,我们可以通过断点的方式进行调试。
接着在你想要的测试的位置上,打上一个断点。
首先我们需要知道的是,下面这段函数是不会自己执行的,因此没有必要打上断点。
我们需要在调用的位置上打上断点。
然后按住F5刷新一下,进入断点调试状态。
按住F11进入函数执行setTimeout函数
然后在按F11往下执行,你会发现会停留两秒钟后,会执行下面这段代码。
# 其实这也说明了,只是进入了setTimeout函数内部执行。其实你细心的话,你会发现,其实setTimeout函数也是一个以函数作为参数的方法。此时执行的是setTimeout的参数中的函数。
其实你细心的话,你会发现,其实在setTimeout函数中,其实也是用了高阶函数(函数作为参数)的形式在编写代码。
接着执行fn()
紧接着会进入eat函数内的参数中,执行参数中的代码段。
# 这一段就是执行过程中最为重要的地方,我们在吃完饭之后,接着就会执行我们定义在外面的活动。
直到eat函数参数内的函数执行完毕
然后会再次跳会setTimeout函数内执行。这个时候setTimeout函数也执行完毕了。
# 可以看出,执行完活动之后,又会回到setTimeout函数中执行剩余的代码。因为这里并没有剩余的代码了,因此才会结束掉setTimeout函数。然后再结束掉eat函数。这样整个代码就算执行完毕了。
其实这个时候就算执行好了整个过程。
# 了解回调函数的执行过程是非常有必要的。这样的话,我们在编写代码的时候才能心中有数。
案例1:myforEach的封装
在遍历数组的时候,有没有想过有了for循环的情况下,为什么官方还提供forEach方法?
要知道在forEach方法没有出来之前,我们可以使用基于原生JS中的循环(就是使用for循环)就可以实现遍历数组中每一项内容。如下所示:
let ary = [12, 15, 9, 28, 10, 22];
for(let i=0;i<ary.length;i++){
// i:代表的是当前循环的索引。
// ary[i]:根据索引获取循环的某一项值。
console.log('索引:'+i+'内容:'+ary[i]);
}
我们把上面这段代码编写在编辑器中。
然后运行一下看看,显示的结果。
# 我们可以看出,通过普通的for循环,就能够实现遍历数组中每一项内容的功能。
但是除了使用for循环以外,我们还可以通过另一种方式来编写。也就是使用forEach方法来进行遍历数组中的元素。代码如下所示:
let ary = [12,15,9,28,10,22];
ary.forEach(
(item,index)=>{
console.log('索引:'+index+'内容:'+item);
}
)
我们把上面这段代码编写在编辑器中。
# 运行之后,你就可以发现使用forEach的方法就是实现相同的效果。
那么这个时候,你有没有想过,forEach方法里面是如何实现数组的遍历的呢?
其实答案很简单,其实forEach方法的本质还是for循环。只不过for循环被封装成了回调函数。for循环和forEach方法本质上是一样的,只是在外界使用的方式上看上去显的不一样。
那么为什么还要多此一举的将for循环封装成forEach方法呢?
其实我们单纯从代码上进行比较的话,就能看出来,在使用forEach方法的时候,要比for循环要好用很多。在使用forEach方法的时候,我们并不需要考虑“i++”这类的递增关系。只需要关注item和index。就是完成遍历的操作。因此通过封装的代码,会让代码变得更加的方便的使用。日后的代码维护也变得方便很多。这就是为什么需要大费周章过的对for循环进行封装的原因。
如何证明forEach方法的本质就是for循环呢?
最直接的方法,自然是直接查看源码。这里我就不介绍了,我们来个更好玩的,我们试试用回调函数的方法来封装一个属于自己的forEach方法。实现和forEach方法相同的效果。
具体封装过程:
我们可以看出这个通过for循环遍历的话,代码是这样的。
而forEach方法,本质上就是对for循环的封装。而封装的方式是通过回调函数进行封装的。
# 回调函数:编写在方法中的函数。
因此首先我们需要编写一个方法用来管理for循环。
然后,我们需要在方法中添加fn参数。其中fn代指的是方法。
接下来,我们就要考虑fn函数的编写情况了。我们需要考虑的是,需要遍历的数据是什么?
但是这会有个问题是,我们不能直接使用ary,因为这样的话会限制我们封装的代码。
我们应该将数组的名称决定权交给调用者。我们可以用this来指代需要遍历的数组名称。
接下来我们重点看一下如何编写fn函数。其实就是对打印出来的结果进行改造。
# 其实这样就算封装完毕了,是不是很简单。
如果是在前端使用的话,我们还需要将自己定义的myForEach方法定义到Array.peototype中。在微信小程序中使用自己定义的myForEach方法的话。只需要引入就可以使用。没有这么麻烦。
打印出来看下结果。
当传递进去之后,那么我们在后续遍历数组的时候,myForEach方法才会起到作用。这个时候我们试着使用起来。
为什么需要使用这么奇怪的调用方法结构。
ary.myForEach()
其实原因在于我们在编写myForEach方法的时候,为了能够封装一个通用性较强的方法,我们用了this来替代数组名称。(前面已经说过)。既然在封装的时候没有规定是哪一个特定的数据,那么我们在使用的时候,就需要先告诉一下myForEach方法需要使用的具体数组名,然后再使用方法。也就是这样的ary.myForEach()结构。
那么这个时候,我们需要如何使用myForEach方法呢?
这个你需要知道的是,index参数内部存储的就是for循环中的索引i,item参数内部存储的是for循环中的值。
然后我们就可以执行遍历数组了。
打印出来看一下效果
# 这就把forEach方法的效果给模拟出来了。可以看出forEach方法本质就是for循环。只不过我们通过回调函数的方式将for循环进行了封装。
细心的你会发现,其实myforEach方法的使用方式和数组内置的forEach方法的使用方法是一样的。
我们还可以完善一下代码,如下所示:
最终打印出来的结果如下:
可以看出,本质上还是通过回调函数的形式进行来封装。
上篇的案例来源于:www.jianshu.com/p/a07975ae2…
案例2:双数组的封装
和封装myForEach方法的原理一样,我们可以封装一下双数组。前提在于我们需要对双数组足够的了解,封装的代码如下:
运行看一下:
案例3:express 回调函数
要知道,现在是大前端的时代,JavaScript已经不再是哪个编写一些跑马灯的年代了,自从有了Node以后,JavaScript已经可以触及后端的领域。这些年里,涌现出很多的框架,比如express和Koa。而这些代码中,会经常使用到回调函数,满眼看上去,到处都是以函数作为参数的回调函数的使用。如果对回调函数理解不了的话,那么对应代码的理解上会变得非常的吃力。
比如下面这段代码,我们进行分析一下。
app.use(function(req, res, next) {
var err = new Error('Not Found');
err.status = 404;
next(err);
});
app是对象,use是方法。
use方法的参数,是一个带有参数的匿名函数。
而最里面这块是函数体。
其实你会发现上面这一块代码也是运用了回调函数的知识点。要知道,在使用nodejs、express 的时候,不可能每个函数我们都要找到它的函数定义去看一看。所以只要知道那个定义里面给 callback 传递了什么参数就行了。然后在调用函数时,在参数里我们自己定义匿名函数来完成某些功能。
二、函数作为返回值
参考教程:
https://www.bilibili.com/video/BV1WE411t7kL?p=57
待完善