【Hello,前端!】浅谈 JavaScript 高阶函数| 青训营笔记

80 阅读4分钟

这是我参与「第四届青训营 」笔记创作活动的第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

今天的高阶函数分享就到这里了,高阶函数是函数式编程非常重要的内容,本文仅做粗浅介绍,意在抛砖引玉。