函数式编程只描述在程序输入上执行的操作,不必使用临时变量保存中间结果。重点是捕捉 “是什么以及为什么”,而不是 “如何做”。与将重点放在执行连续命令上的过程性编程相比,函数式编程的重点是函数的定义而不是状态机(state machine)的实现。
大型知识管理系统应用程序从使用函数式编程风格上受益颇多,因为函数式编程简化了开发。
在那些通过描述 “如何做” 指定解决问题的方法的语言中,许多开发人员都知道如何进行编码。例如,要编写一个计算阶乘的函数,我可以编写一个循环来描述程序,或者使用递归来查找所有数字的乘积。在这两种情况下,计算的过程都在程序中进行了详细说明。 清单1显示了一个计算阶乘的可能使用的 C 代码。
int factorial (int n) { if (n <= 0) return 1; else return n * factorial (n-1); } |
这类语言也叫做过程性 编程语言,因为它们定义了解决问题的过程。函数式编程与这个原理有显著不同。在函数式编程中,需要描述问题 “是什么”。 函数式编程语言又叫做声明性 语言。同样的计算阶乘的程序可以写成所有到 n 的数字的乘积。计算阶乘的典型函数式程序看起来如 清单2 中的示例所示。
factorial n, where n <= 0 := 1 factorial n := foldr * 1 take n [1..] |
第二个语句指明要得到从 1 开始的前 n 个数字的列表(take n [1..]
),然后找出它们的乘积,1 为基元。这个定义与前面的示例不同,没有循环或递归。它就像阶乘函数的算术定义。一旦了解了库函数(take
和 foldr
)和标记(list notation [ ]
)的意义,编写代码就很容易,而且可读性也很好。
从 历史上看,函数式编程语言不太流行有各种原因。但是最近,有些函数式编程语言正在进入计算机行业。其中一个例子就是 .NET 平台上的 Haskell。其他情况下,现有的一些语言借用了函数式编程语言中的一些概念。一些 C++ 实现中的迭代器和 continuation,以及 JavaScript 中提供的一些函数式构造(functional construct),就是这种借用的示例。但是,通过借用函数式构造,总的语言编程范例并没有发生变化。JavaScript 并没因为函数式构造的添加就变成了函数式编程语言。
我现在要讨论 JavaScript 中的函数式构造的各种美妙之处,以及在日常编码和工作中使用它们的方式。我们将从一些基本功能开始,然后用它们查看一些更有趣的应用。
在 JavaScript 中,可以编写匿名函数或没有名称的函数。为什么需要这样的函数?请继续往下读,但首先我们将学习如何编写这样一个函数。如果拥有以下 JavaScript 函数:
清单 3. 典型的函数
function sum(x,y,z) { return (x+y+z); } |
然后对应的匿名函数看起来应当如下所示:
清单 4. 匿名函数
function(x,y,z) { return (x+y+z); } |
要使用它,则需要编写以下代码:
var sum = function(x,y,z) { return (x+y+z); }(1,2,3); alert(sum); |
也可以将函数作为值使用。还可以拥有一些所赋值是函数的变量。在最后一个示例中,还可以执行以下操作:
清单 6. 使用函数赋值
var sum = function(x,y,z) { return (x+y+z); } alert(sum(1,2,3)); |
在上面清单6的示例中,为变量 sum 赋的值是函数定义本身。这样,sum 就成了一个函数,可以在任何地方调用。
JavaScript 允许用两种方式调用函数,如清单7和8所示。
alert (“Hello, World!"); |
或
(alert) (“Hello, World!"); |
所以也可以编写以下代码:
( function(x,y,z) { return (x+y+z) } ) (1, 2, 3); |
可以在括号中编写函数表达式,然后传递给参数,对参数进行运算。虽然在 清单8的示例中,有直接包含在括号中的函数名称,但是按 清单9 中所示方式使用它时,就不是这样了。
也可以将函数作为参数传递给其他函数。虽然这不是什么新概念,但是在后续的示例中大量的使用了这个概念。可以传递函数参数,如 清单10 所示。
var passFunAndApply = function (fn,x,y,z) { return fn(x,y,z); }; var sum = function(x,y,z) { return x+y+z; }; alert( passFunAndApply(sum,3,4,5) ); // 12 |
执行最后一个 alert 语句输出了一个大小为 12 的值。
前一节介绍了一些使用函数式风格的编程概念。所给的示例并没有包含所有的概念,它们在重要性方面也没有先后顺序,只是一些与这个讨论有关的概念而已。下面对 JavaScript 中的函数式风格作一快速总结:
这一节将介绍一些有效使用这些概念编写优美的 JavaScript 代码的示例。(使用 JavaScript 函数式风格,可以做许多超出这个讨论范围的事。)
function (x,y) { return x.date – y.date; } |
要得到需要的函数,请使用清单12的示例。
arr.sort( function (x,y) { return x.date – y.date; } ); |
其中 arr 是类型数组对象。排序函数会根据 arr 数组中对象的日期对所有对象进行排序。比较函数和它的定义一起被传递给排序函数,以完成排序操作。使用这个函数:
qsort
函数类似。清单13中的代码风格通常被用来从数组生成动态 HTML。
var str=' '; for (var i=0;i<arr.length;i++) { var element=arr[i]; str+=... HTML generation code... } document.write(str); |
可以用 清单14的代码替换这个代码。
Array.prototype.fold=function(templateFn) { var len=this.length; var str=' '; for (var i=0 ; i<len ; i++) str+=templateFn(this[i]); return str; } function templateInstance(element) { return ... HTML generation code ... } document.write(arr.fold(templateInstance)); |
我使用 Array
类型的 prototype 属性定义新函数 fold。现在可以在后面定义的任何数组中使用该函数。
window.setTimeout
函数,该函数有两个参数。第一个参数是在第二个参数表示的毫秒数之后被调用的函数。清单15 显示了完成此操作的一种方法。
window.setTimeout(function(){alert(‘First!’);alert(‘Second!’);}, 5000); |
Function.prototype.sequence=function(g) { var f=this; return function() { f();g(); } }; function alertFrst() { alert(‘First!’); } function alertSec() { alert(‘Second!’); } setTimeout( alertFrst.sequence(alertSec), 5000); |
在处理事件时,如果想在调用完一个回调之后再调用一个回调,也可以使用 清单16中的代码扩展。这可能是一个需要您自行完成的一个练习,现在您的兴趣被点燃了吧。