这是我参与「第四届青训营 」笔记创作活动的第4天
今天开始 JavaScript 的内容,对于 JavaScript 的基础语法本文将不再赘述,将直接以高阶函数为例展开介绍。
高阶函数
高阶函数,即 Higher-order function,它表示满足以下条件之一的函数:
- 将函数作为参数
- 将函数作为返回值
下面我们来看一个简单的例子:
function factorial(a) {
a = parseInt(a)
if(a === 1)
return 1
return a * factorial(a - 1)
}
function abs(a) {
if(a < 0)
return -a
return a
}
function sum(numbers, fn) {
let res = 0
for(let i = 0;i < numbers.length;i++) {
res += fn(i)
}
return res
}
在上面的代码中,factorial 和 abs 都是普通低阶函数,它们分别用于求一个数的阶乘和绝对值,而 sum 是一个高阶函数,它的参数中有一个函数 fn,他将数组 numbers 中的数字遍历并使用 fn 函数进行处理,最后将所得返回值求和。
通过比较我们也不难看出,初阶函数的作用是比较明晰且确定的。但高阶函数由于他依赖于初始函数去实现具体的功能,所以高阶函数相对更加抽象,同时具备更好的扩展性,可以通过改变参数里的函数来实现不同的功能。
例如通过 sum(numbers, abs)
我们可以对一个数组求绝对值之和,使用 sum(numbers, factorial)
我们可以对一个正整数组求阶乘之和。
JavaScript 原生高阶函数
JavaScript 中原生就有非常多的高阶函数,一些函数在日常编程中也使用的非常频繁,下面从 ES6 新增的几种高阶函数进行简要介绍。
map
map 函数相信大家使用得非常多了,它的作用就是对一个数组按照某种映射规则进行进行映射处理,从而得到一个与原数组长度相同的映射数组。
let numbers = [1, 2, 3, 4, 5]
let newArr = numbers.map(item => {
return item * 2
})
// newArr = [2, 4, 6, 8, 10]
上面的例子中,我们就使用了 map 方法将数组中的每一个元素都放大到两倍数值,得到了一个新的数组。在实际编程中,我们可以通过自定义映射函数方便快捷的完成许多复杂的功能,例如提取一个复杂的班级学生信息JSON对象数组中的姓名属性,组成一个只有姓名的字符串数组。
filter
filter 也是一个比较常用的函数,他也是对一个数组进行遍历,然后将函数返回值作为筛选条件,返回满足筛选条件(即函数返回值为真)的所有数组元素。
let info = [{
name: '张三',
grade: 54
}, {
name: '李四',
grade: 92
}, {
name: '王五',
grade: 60
}]
let pass = info.filter(x => {
return x.grade >= 60
}
/* pass = [{
name: '王五',
grade: 60
}]
*/
上面的例子中,我们就使用了 filter 方法将班级信息中成绩超过或等于60分的人的信息筛选了出来。如果我们使用 for 循环去遍历的话,就没有使用高阶函数那么语义化和直观。
sort
sort 函数也是对数组进行操作的高阶函数,他的函数参数需要两个参数,表示数组里的两个相邻元素,函数的返回值 TRUE or FALSE 决定了两个元素是否进行交换。
let info = [{
name: '张三',
grade: 54
}, {
name: '李四',
grade: 92
}, {
name: '王五',
grade: 60
}]
let pass = info.sort((a, b) => {
return a - b
}
/* pass = [{
name: '李四',
grade: 92
}, {
name: '王五',
grade: 60
}, {
name: '张三',
grade: 54
}]
上面的例子中,通过 sort 方法就将班级信息中的学生按照成绩由高到低进行排序了。使用 sort 的实现排序就明显比我们手动去写排序算法要简洁太多了。
常用的自定义高阶函数
在编程开发中,有一些高阶函数是非常实用且必要的,我们往往需要根据自身需求去编写一些高阶函数以便开发使用。下面介绍两种常用的高阶函数:防抖与节流。
防抖
当我们在一些监听事件中,如果对象变化非常频繁,但是又没有必要实时监听每一次变化,我们只关注变化最终停止的状态时就可以使用防抖。例如输入联想功能,当用户正在输入时输入框内不断改变的字符我们不需要关注,我们只需要在用户输入完后为用户检索并提供联想结果即可。
function antiShake(fn, delay = 200) {
let timer = null
return function() {
clearTimeout(timer)
timer = setTimeout(() => {
fn.apply(this, arguments)
}, delay)
}
}
在上面的代码中,我们使用定时器实现防抖。如果输入框的内容变化很快,在上一个定时器还没有执行时就再次调用了函数,就会清除上一次的定时器,使得联想操作不作用。直到在定时器设定的时间内都没有新的变化导致调用,才会去执行相应的操作。
节流
在某些情况下,我们需要监听某个事件的变化过程,但是我们不希望太过频繁的去响应。例如滚动条滚到页面底部时显示一个回到顶部按钮,如果实时监听滚动事件,就会非常高频的去执行触发操作,非常的耗费性能。所以我们需要人为的控制事件触发频率在一个合适的范围,这个时候就需要用到节流。
function throttle(fn, delay = 200) {
let timer = null
return function () {
if(!timer) {
timer = setTimeout(() => {
fn.apply(this, arguments)
timer = null
}, delay)
}
}
}
这一段代码整体看上去和防抖非常相似,同样使用到了定时器。节流的实现逻辑是,如果上一个定时器没有执行,则不会允许创建新的定时器,所以最大触发频率由定时器的设定时间所限制,实现了节流。
End
今天的高阶函数分享就到这里了,高阶函数是函数式编程非常重要的内容,本文仅做粗浅介绍,意在抛砖引玉。