
要说JavaScript和其他较为常用的语言最大的不同是什么,那无疑就是JavaScript是函数式的语言,函数式语言的特点如下:
函数为第一等的元素,即人们常说的一等公民。就是说,在函数式编程中,函数是不依赖于其他对象而独立存在的(对比与Java,函数必须依赖对象,方法是对象的方法)。
函数可以保持自己内部的数据,函数的运算对外部无副作用(修改了外部的全局变量的状态等),关于函数可以保持自己内部的数据这一特性,称之为闭包。我们可以来看一个简单的例子:
Js代码
var outter = function(){
var x = 0;
return function(){
return x++;
}
}
var a = outter();
print(a());
print(a());
var b = outter();
print(b());
print(b());
运行结果为:
0
1
0
1
变量a通过闭包引用outter的一个内部变量,每次调用a()就会改变此内部变量,应该注意的是,当调用a时,函数outter已经返回了,但是内部变量x的值仍然被保持。而变量b也引用了outter,但是是一个不同的闭包,所以b开始引用的x值不会随着a()被调用而改变,两者有不同的实例,这就相当于面向对象中的不同实例拥有不同的私有属性,互不干涉。
由于JavaScript支持函数式编程,我们随后会发现JavaScript许多优美而强大的能力,这些能力得力于以下主题:匿名函数,高阶函数,闭包及柯里化等。熟悉命令式语言的开发人员可能对此感到陌生,但是使用lisp, scheme等函数式语言的开发人员则觉得非常亲切。
9.1匿名函数
匿名函数在函数式编程语言中,术语成为lambda表达式。顾名思义,匿名函数就是没有名字的函数,这个是与日常开发中使用的语言有很大不同的,比如在C/Java中,函数和方法必须有名字才可以被调用。在JavaScript中,函数可以没有名字,而且这一个特点有着非凡的意义:
Js代码
function func(){
//do something
}
var func = function(){
//do something
}
这两个语句的意义是一样的,它们都表示,为全局对象添加一个属性func,属性func的值为一个函数对象,而这个函数对象是匿名的。匿名函数的用途非常广泛,在JavaScript代码中,我们经常可以看到这样的代码:
Js代码
var mapped = [1, 2, 3, 4, 5].map(function(x){return x * 2});
print(mapped);
应该注意的是,map这个函数的参数是一个匿名函数,你不需要显式的声明一个函数,然后将其作为参数传入,你只需要临时声明一个匿名的函数,这个函数被使用之后就别释放了。在高阶函数这一节中更可以看到这一点。
9.2高阶函数
通常,以一个或多个函数为参数的函数称之为高阶函数。高阶函数在命令式编程语言中有对应的实现,比如C语言中的函数指针,Java中的匿名类等,但是这些实现相对于命令式编程语言的其他概念,显得更为复杂。
9.2.1 JavaScript中的高阶函数
Lisp中,对列表有一个map操作,map接受一个函数作为参数,map对列表中的所有元素应用该函数,最后返回处理后的列表(有的实现则会修改原列表),我们在这一小节中分别用JavaScript/C/Java来对map操作进行实现,并对这些实现方式进行对比:
Js代码
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是一个高阶函数。我们进行测试如下:
Js代码
function double(x){
return x * 2;
}
[1, 2, 3, 4, 5].map(double);//return [2, 4, 6, 8, 10]
应该注意的是double是一个函数。根据上一节中提到的匿名函数,我们可以为map传递一个匿名函数:
Js代码
var mapped = [1, 2, 3, 4, 5].map(function(x){return x * 2});
print(mapped);
这个示例的代码与上例的作用是一样的,不过我们不需要显式的定义一个double函数,只需要为map函数传递一个“可以将传入参数乘2并返回”的代码块即可。再来看一个例子:
Js代码
[
{id : "item1"},
{id : "item2"},
{id : "item3"}
].map(function(current){
print(current.id);
});
将会打印:
item1
item2
item3
也就是说,这个map的作用是将传入的参数(处理器)应用在数组中的每个元素上,而不关注数组元素的数据类型,数组的长度,以及处理函数的具体内容。