在JavaScript中,由于函数名就是一个变量,所以函数也可以作为值来使用。我们不仅可以像传递参数一样把一个函数传递给另一个函数,还可以将一个函数作为另一个函数的返回值返回。
将函数作为参数传递
由于函数是对象,所以可以直接把函数作为参数来传递。来看下面的例子:
//定义一个函数
function say(str){
return "hello "+str;
}
//调用函数
alert(say("JavaScript")); //返回“hello JavaScript”
上面的代码中定义了一个函数say()
,它接收一个字符串参数,调用这个函数后会返回字符串"hello 参数字符串"。上面的函数定义和调用方法是我们通常的做法,但是这种函数调用缺乏灵活性。我们可以通过将函数作为参数的方式来编写一个通用函数。这个通用函数可以调用任何参数匹配的函数。
//定义一个通用函数
function callFun(fun,arg){
//第一个参数是函数对象
return fun(arg);
}
上面的callFun()函数是一个通用函数,它的第一个参数是一个函数对象,并且它的返回值就是参数中的函数的执行结果。使用这个通用函数来执行前面定义的say()
函数的方法为:
callFun(say,"JavaScript"); //返回“hello JavaScript”
可以看到,不论是什么函数,只要它只有一个参数,那么就可以使用callFun()
函数来进行调用。
函数作为返回值
JavaScript函数更为神奇的地方是可以将一个函数作为另一个函数的返回值进行返回。这样做给我们带来的最大的好处是内部函数的参数作用域被延长了。来看下面的例子:
function fn1(arg){
var rel = function(num){
return arg + num;
}
//此时返回的是一个函数对象
return rel;
}
上面的fn1()
函数中定义了另一个函数rel()
,rel()
函数的返回值是fn1()
函数的参数和rel()
函数的参数相加的结果。最后,fn1()
函数将整个rel
作为结果返回。我们可以像下面这样来调用这个函数:
var f = fn1(20);
alert(f(11)); //arg的作用域被扩大了
可以看到,对内部函数rel
的赋值是在最后的f()
函数中进行的,而此时对fn1()
函数的调用已经完成,按理说在完成f = fn1(20)
操作之后,作为参数的20就应该被回收了,但是由于在fn1()
函数内部将rel
作为返回值返回,使得fn1()
函数的参数arg
的作用域被延长。
将函数作为结果返回的好处是我们可以在函数执行的时候才确定内部函数的参数,这样给函数的调用带来了极大的灵活性。
下面我们以排序为例子,说明函数作为参数和作为返回值的使用方法。
在介绍数组对象的时候我们曾经说过,数组对象有一个sort()
方法,用于对数组元素进行排序。看下面的例子:
var arr = [1,2,215,16,58,35,8];
console.info(arr.sort()); //对于JavaScript而言,默认是按字符串来进行排序
因为对于JavaScript而言,默认是按字符串来进行排序,所以我们得到的返回结果是:[1, 16, 2, 215, 35, 58, 8]
。
如果我们需要按数值的大小来进行排序,在sort()
方法中有一个可选的参数sortOrder
。这个参数可以决定排序的方式,sortOrder
是排序函数的名称,而且这个函数有2个参数,并要求返回一个整数值。如果返回大于0的值,那么第一个参数大于第二个参数,如果返回等于0的值,那么两个参数相等,如果返回小于0的值,那么第一个参数小于第二个参数。现在我们来编写sortOrder
方法。
//按数值大小排序的方法
function sortOrder(a,b){
return a-b;
}
上面的排序函数有2个参数a和b,并且返回值符合排序要求。下面我们使用这个排序函数来对数组arr重新排序。
//使用自定义排序方法重新排序
console.info(arr.sort(sortOrder));
得到的结果如下图所示:
现在,我们得到了按数值大小进行排序的正确结果。对sort()
方法进行自定义排序就是一个使用函数作为参数进行传递的最好的例子。
下面我们再来研究一下对对象进行排序。看下面的例子:
function Person(name,age){
this.name = name;
this.age = age;
}
var p1 = new Person("John",23);
var p2 = new Person("Tom",33);
var p3 = new Person("Jack",28);
var pArr = [p1,p2,p3];
上面的代码中创建了一个Person
对象类型,并通过Person
类型来实例化了3个对象,分别赋予他们不同的name
和age
属性。并将它们放入一个数组中,准备进行排序。
接下来我们自定义两个排序方法,分别按名字和年龄进行排序。
//按名字排序
function sortByName(obj1,obj2){
if(obj1.name > obj2.name) {
return 1;
}else if(obj1.name == obj2.name) {
return 0;
}else {
return -1;
}
}
//按年龄排序
function sortByAge(obj1,obj2){
return obj1.age - obj2.age;
}
通过将上面定义的2个排序函数作为参数传入到sort()
方法中,我们就可以得到想要的结果。
//按名字排序
pArr.sort(sortByName);
//输出结果
for(var i = 0; i < pArr.length; i++){
console.info(pArr[i].name + "," + pArr[i].age);
}
按名字排序的结果如下图所示:
//按年龄排序
pArr.sort(sortByAge);
//输出结果
for(var i = 0; i < pArr.length; i++){
console.info(pArr[i].name + "," + pArr[i].age);
}
按年龄排序的结果如下图所示:
上面的做法可以达到对象按属性排序的目的,但是如果我们的对象中有100个属性,现在要求对每一个属性都进行不同的排序,是不是我们要编写100个不同的排序方法呢?答案是否定的,我们通过将排序函数作为函数的返回值进行返回,在执行排序的时候再将要排序的属性传入其中,就可以完美的解决这个问题。看下面的代码:
//通用的按对象属性进行排序的函数
function sortByProperty(propertyName){
var sortFun = function(obj1,obj2){
if(obj1[propertyName] > obj2[propertyName]){
return 1;
}else if(obj1[propertyName] == obj2[propertyName]){
return 0;
}else{return -1;}
}
return sortFun;
}
我们可以像下面这样来调用这个排序函数:
//按名字排序
pArr.sort(sortByProperty("name"));
//按年龄排序
pArr.sort(sortByProperty("age"));
在一些高级JavaScript框架中(如jQuery),大量使用函数作为参数来进行传递,以及使用函数作为另一个函数的返回值,要读懂这些框架,必须非常透彻的理解JavaScript的函数传值机制。