kaola-fed / blog

kaola blog

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

高阶函数入门指北

woodbrick opened this issue · comments

1 引言

作为一等公民,函数在JavaScript中拥有重要的地位,函数本身的特性也使得JavaScript能够轻易并且优雅的处理其他语言难以处理的问题。

JavaScript中的函数,有以下两个特性:

  1. 闭包
  2. 函数本身是一个变量

而高阶函数正是利用这两个特性,来实现的。

2 正文

由于JavaScript的函数指向某个变量。既然变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数。

举个栗子:

    function add(a, b, func) {
        return func(a) + func(b);
    }

这是一个最简单的高阶函数结构,那么他能用来做什么的呢?

    add(-5, 5, Math.abs)

大家想一下,他返回的值是什么呢?

没错,通过这个高阶函数,我们把两个数字的绝对值相加了。
写到这里,大家也许会想,其实我直接调用两次绝对值函数,也能够很快实现同样的功能啊?

    function addByAbs(a, b) {
        return Math.abs(a) + Math.abs(b);
    }

那么,这两种方式,本质有什么区别呢?

在于变量,通过高阶函数,我们可以灵活控制求和的前置方法。

如果现在我们有一份数据:

    let scores = [
        {
            name: '张三',
            math: 88,
            english: 66,
            chinese: 77
        },
        {
            name: '李四',
            math: 66,
            english: 99,
            chinese: 99
        },
        {
            name: '赵五',
            math: 77,
            english: 88,
            chinese: 88
        }
    ];

我们应该要如何把同学的成绩,按照总分降序排序呢?

    scores.sort((a, b) => 
        (b.math + b.english + b.chinese)
        - (a.math + a.english + a.chinese)
    )

JavaScript原生的sort方法,支持我们传入一个比较器,来对数组中的元素进行比较,从而实现排序算法和排序业务逻辑的分离。
它接受了一个函数作为参数,使得我们的业务逻辑可以灵活的变动,而不影响排序的代码。

如果此时老师说,对于理科生,数学更重要,数学成绩需要乘以1.5,此时我们去修改排序的算法,应该怎么写呢?

    scores.sort((a, b) => 
        (b.math * 1.5 + b.english + b.chinese)
        - (a.math + a.english + a.chinese)
    )

我们快速的修改了代码,然后运行,糟糕,结果出错了!

原来我们把成绩求和的算法写了两遍,漏改了其中一份,实际应该是

    scores.sort((a, b) => 
        (b.math * 1.5 + b.english + b.chinese)
        - (a.math * 1.5 + a.english + a.chinese)
    )

但是这段代码,看起来不太优雅,也产生了一定的维护成本,因为我们手写了两遍求总分的算法,需求变动的时候,需要修改两处,容易错漏。

那么我们是否可以通过高阶函数的**,来进行处理呢?
大家回忆一下开篇的代码,不难得出一下结论

    function getTotalScore(score){
        score.math * 1.5 + score.english + score.chinese
    }
    
    function compare(a, b, func) {
        return func(b) - func(a);
    }

但是问题又来了,sort函数的参数,是一个比较器方法,只允许传递两个元素,这种情况,要怎么处理呢?

    function getTotalScore(score){
        score.math * 1.5 + score.english + score.chinese
    }
    
    function getDescComparator(func) {
        return function(a, b) {
            return func(b) - func(a);
        };
    }

    let scoreComparator = getDescComparator(getTotalScore);

    scores.sort(scoreComparator);

我们现在获得了两个方法:

1.业务逻辑相关的方法getTotalScore,这部分表示如何获取一个学生的排序分数

2.高阶函数getDescComparator,他将获取比较分数的方法作为参数,返回一个比较方法,当然在这个过程中,形成了一个闭包,即比较器持有了getTotalScore这个变量(函数)

现在我们回顾一下开篇:

JavaScript的函数特性有

  1. 闭包
  2. 函数本身是一个变量

而高阶函数正是利用这两个特性,来实现的。

小结

高阶函数提供了我们两种解决问题的思路:

  1. 我们可以将需要动态处理的问题,封装成单独的函数,并且传递给不同的使用场景。
  2. 我们可以将一个函数进行变化,获取新的函数,最终在函数之间,构建一种排列组合的关系,而不是通过重复劳动来写很多相似的代码。

最后,送大家一个小福利:

    function thunkingDebug(func) {
        return function() {
            try {
                func.apply(this, arguments)
            } catch (e) {
                window.location.href =
                "http://stackoverflow.com/search?q=[js]+" + e.message;
            }
        }
    }

by kaolafed/yaofeng

参考文档:

高阶函数