JavaScript入门教程

JavaScript简介
JavaScript语法基础
JavaScript流程控制
JavaScript函数
面向对象编程
JavaScript事件
JavaScript DOM
正则表达式
JavaScript BOM
AJAX

专题分析

浏览器兼容性
JS优化
Web前端开发规范
编辑器推荐
总结和笔记

学习助手

对象参考手册
ECMAScript分析
数据中心
QQ交流群

高阶函数

通常,以一个或多个函数为参数的函数称之为高阶函数。高阶函数在命令式编程语言中有对应的实现,比如 C 语言中的函数指针,Java 中的匿名类等,但是这些实现相对于命令式编程语言的其他概念,显得更为复杂。

JavaScript 中的高阶函数

Lisp 中,对列表有一个 map 操作,map 接受一个函数作为参数,map 对列表中的所有元素应用该函数,最后返回处理后的列表(有的实现则会修改原列表),我们在这一小节中分别用 JavaScript/C/Java 来对 map 操作进行实现,并对这些实现方式进行对比:
Array.prototype.map = function(func /*, obj */){
    var len = this.length;
    //check the argument
    if(typeof func != "function"){
        throw new Error("argument should be a function!");
    }
    var res = [];
    var obj = arguments[1];
    for(var i = 0; i < len; i++){
        //func.call(), apply the func to this[i]
        res[i] = func.call(obj, this[i], i, this);
    }
    return res;
}

我们对 JavaScript 的原生对象 Array 的原型进行扩展,函数 map 接受一个函数作为参数,然后对数组的每一个元素都应用该函数,最后返回一个新的数组,而不影响原数组。由于map 函数接受的是一个函数作为参数,因此 map 是一个高阶函数。我们进行测试如下:
function double(x){
    return x * 2;
}
[1, 2, 3, 4, 5].map(double);//return [2, 4, 6, 8, 10]

应该注意的是 double 是一个函数。根据上一节中提到的匿名函数,我们可以为 map 传递一个匿名函数:
    var mapped = [1, 2, 3, 4, 5].map(function(x){return x * 2});
    print(mapped);

这个示例的代码与上例的作用是一样的,不过我们不需要显式的定义一个 double 函数,只需要为 map 函数传递一个“可以将传入参数乘 2 并返回”的代码块即可。再来看一个例子:
[
    {id : "item1"},
    {id : "item2"},
    {id : "item3"}
].map(function(current){
    print(current.id);
});

将会打印:
item1
item2
item3

也就是说,这个 map 的作用是将传入的参数(处理器)应用在数组中的每个元素上,而不关注数组元素的数据类型,数组的长度,以及处理函数的具体内容。

C 语言中的高阶函数

C 语言中的函数指针,很容易实现一个高阶函数。我们还以 map 为例,说明在 C 语言中如何实现:
    //prototype of function
    void map(int* array, int length, int (*func)(int));
map 函数的第三个参数为一个函数指针,接受一个整型的参数,返回一个整型参数,我们来看看其实现:
    //implement of function map
    void map(int* array, int length, int (*func)(int)){
        int i = 0;
        for(i = 0; i < length; i++){
            array[i] = func(array[i]);
        }
    }
我们在这里实现两个小函数,分别计算传入参数的乘 2 的值,和乘 3 的值,然后进行测试:
int twice(int num) { return num * 2; }
int triple(int num){ return num * 3; }
//function main
int main(int argc, char** argv){
    int array[5] = {1, 2, 3, 4, 5};
    int i = 0;
    int len = 5;
    //print the orignal array
    printArray(array, len);
    //mapped by twice
    map(array, len, twice);
    printArray(array, len);
    //mapped by twice, then triple
    map(array, len, triple);
    printArray(array, len);
    return 0;
}

运行结果如下:
1 2 3 4 5
2 4 6 8 10
6 12 18 24 30

应该注意的是 map 的使用方法,如 map(array, len, twice)中,最后的参数为 twice,而twice 为一个函数。因为 C 语言中,函数的定义不能嵌套,因此不能采用诸如 JavaScript
中的匿名函数那样的简洁写法。

虽然在 C 语言中可以通过函数指针的方式来实现高阶函数,但是随着高阶函数的“阶”的增高,指针层次势必要跟着变得很复杂,那样会增加代码的复杂度,而且由于 C 语言是强类型的,因此在数据类型方面必然有很大的限制。

Java 中的高阶函数

Java 中的匿名类,事实上可以理解成一个教笨重的闭包(可执行单元),我们可以通过 Java的匿名类来实现上述的 map 操作,首先,我们需要一个对函数的抽象:
    interface Function{
        int execute(int x);
    }
我们假设 Function 接口中有一个方法 execute,接受一个整型参数,返回一个整型参数,然后我们在类 List 中,实现 map 操作:
private int[] array;
public List(int[] array){
    this.array = array;
}
public void map(Function func){
    for(int i = 0, len = this.array.length; i < len; i++){
        this.array[i] = func.execute(this.array[i]);
    }
}

map 接受一个实现了 Function 接口的类的实例,并调用这个对象上的 execute 方法来处理数组中的每一个元素。我们这里直接修改了私有成员 array,而并没有创建一个新的数组。

好了,我们来做个测试:
public static void main(String[] args){
    List list = new List(new int[]{1, 2, 3, 4, 5});
    list.print();
    list.map(new Function(){
        public int execute(int x){
            return x * 2;
        }
    });
    list.print();
    list.map(new Function(){
        public int execute(int x){
            return x * 3;
        }
    });
    list.print();
}

同前边的两个例子一样,这个程序会打印:
1 2 3 4 5
2 4 6 8 10
6 12 18 24 30

灰色背景色的部分即为创建一个匿名类,从而实现高阶函数。很明显,我们需要传递给 map的是一个可以执行 execute 方法的代码。而由于 Java 是命令式的编程语言,函数并非第一位的,函数必须依赖于对象,附属于对象,因此我们不得不创建一个匿名类来包装这个execute 方法。而在 JavaScript 中,我们只需要传递函数本身即可,这样完全合法,而且代码更容易被人理解。