用函数式编程技术编写优美的 JavaScript
本文深入探讨如何在 JavaScript 中运用函数式编程思想编写更优雅的代码。通过解析匿名函数、函数作为一等公民、以及将函数作为参数传递等核心概念,结合数组排序、动态 HTML 生成及回调序列执行等实际案例,展示如何利用函数式风格简化逻辑、提升代码可读性与复用性。
引言:函数式编程与命令式的区别
由于函数式编程采用了一种截然不同的程序组织方式,习惯了命令式编程范式的开发者可能会觉得它难以掌握。本文将通过具体示例,展示如何采用函数式风格,用 JavaScript 编写出结构清晰、逻辑优美的代码。主要内容包括:
函数式编程的核心概念,涵盖匿名函数的定义、多种函数调用方式,以及如何将函数作为参数传递给其他函数。
函数式概念的实际应用,示例包括:如何扩展数组排序功能、生成动态 HTML 的优雅代码实现,以及系列函数的链式应用。
函数式编程核心概念解析
函数式编程基础概念
请分享给更多人。您可以将此文章提交到:
Digg
Slashdot
在许多通过描述“如何做”来指定解决方案的语言中,许多开发人员已经掌握了编码技巧。例如,若要编写一个计算阶乘的函数,可以编写一个循环来描述执行过程,或者使用递归来计算所有数字的乘积。在这两种情况下,计算的具体过程都在程序中进行了详细规定。清单 1 展示了一段用于计算阶乘的 C 语言代码示例。
清单 1. 过程风格的阶乘计算
int factorial (int n)
{
if (n <= 0)
return 1;
else
return n * factorial (n-1);
}
这类语言也被称为过程性编程语言,因为它们定义了解决问题的具体步骤。函数式编程与此原理有显著不同。在函数式编程中,重点在于描述问题“是什么”,即声明结果而非过程。因此,函数式编程语言也被称为声明性语言。同样的阶乘计算程序,可以表述为从 1 到 n 所有数字的乘积。典型的函数式阶乘程序如清单 2 所示。
清单 2. 函数式风格的阶乘定义
factorial n, where n <= 0 := 1
factorial n := foldr * 1 take n [1..]
第二条语句定义了如何获取从 1 开始的前 n 个数字列表(通过 take n [1..]),然后计算它们的乘积,并以 1 作为初始值。这个定义与前一个示例不同,没有使用循环或递归。它更像是对阶乘函数的算术定义。一旦理解了库函数(take 和 foldr)以及列表表示法 [ ] 的含义,编写代码就变得简单且具有良好的可读性。
仅用三行 Miranda 代码即可编写例程,根据参数,使用广度优先或深度优先遍历处理 n 叉树的每个节点,且节点元素可以是任何通用类型。
从历史上看,函数式编程语言因各种原因并不流行。然而最近,一些函数式编程语言正在进入计算机行业。其中一个例子是 .NET 平台上的 Haskell。在其他情况下,现有的一些语言借用了函数式编程语言中的一些概念。例如,某些 C++ 实现中的迭代器和延续(continuation),以及 JavaScript 中提供的一些函数式构造(functional construct),就是这种借用的示例。但是,通过借用函数式构造,总的语言编程范式并没有发生根本变化。JavaScript 并没有因为添加了函数式构造而变成纯粹的函数式编程语言。
接下来,我将讨论 JavaScript 中各种函数式构造的美妙之处,以及在日常编码工作中使用它们的方法。我们将从一些基本功能开始,然后利用它们探索一些更有趣的应用场景。
JavaScript 中的函数式构造
匿名函数
在 JavaScript 中,可以编写匿名函数,即没有名称的函数。为什么需要这样的函数?请继续阅读下文,但首先我们将学习如何编写这样一个函数。如果拥有以下 JavaScript 函数:
清单 3. 典型的命名函数
function sum(x,y,z) {
return (x+y+z);
}
那么对应的匿名函数看起来应当如下所示:
清单 4. 匿名函数定义
function(x,y,z) {
return (x+y+z);
}
要使用它,则需要编写以下代码:
清单 5. 应用匿名函数
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 所示。
清单 7. 典型的函数应用
alert (“Hello, World!");
或者
清单 8. 用函数作为表达式
(alert) (“Hello, World!");
因此,也可以编写以下代码:
清单 9. 定义函数之后立即使用它
( function(x,y,z) { return (x+y+z) } ) (1, 2, 3);
可以在括号中编写函数表达式,然后将其作为参数传递,并对参数进行运算。虽然在清单 8 的示例中,函数名称直接包含在括号中,但在按清单 9 所示方式使用时,情况则不同。
将函数作为参数传递给其他函数
还可以将函数作为参数传递给其他函数。虽然这不是什么新概念,但在后续的示例中大量使用了这个概念。可以传递函数参数,如清单 10 所示。
清单 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 函数式风格,可以做许多超出这个讨论范围的事。)
扩展数组排序
首先编写一个排序方法,可以根据数组元素的日期对数据进行排序。用 JavaScript 编写这个方法非常简单。数据对象的排序方法接受一个可选参数,这个可选参数就是比较函数。在这里,需要使用清单 11 中的比较函数。
清单 11. 比较函数定义
function (x,y) {
return x.date – y.date;
}
要得到需要的函数,请使用清单 12 的示例。
清单 12. 排序函数的扩展实现
arr.sort( function (x,y) { return x.date – y.date; } );
其中 arr 是类型数组对象。排序函数会根据 arr 数组中对象的日期对所有对象进行排序。比较函数和它的定义一起被传递给排序函数,以完成排序操作。使用这个函数:
每个 JavaScript 对象都有一个 date 属性。
JavaScript 的数组类型的排序函数接受可选参数,可选参数是用来排序的比较函数。这与 C 库中的 qsort 函数类似。
动态生成 HTML 的优雅代码
在这个示例中,将展示如何编写优美的代码,从数组动态地生成 HTML。可以根据从数据中得到的值生成表格。或者,也可以用数组的内容生成排序和未排序的列表。还可以生成垂直或水平的菜单项目。
清单 13 中的代码风格通常被用来从数组生成动态 HTML。
清单 13. 生成动态 HTML 的普通代码
var str=' ';
for (var i=0;i<arr.length;i++) {
var element=arr[i];
str+=... HTML generation code...
}
document.write(str);
可以用清单 14 的代码替换这个代码。
清单 14. 生成动态 HTML 的通用方式
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 显示了完成此操作的一种方法。
清单 15. 在回调中调用一组函数
window.setTimeout(function(){alert(‘First!');alert(‘Second!');}, 5000);
清单 16 显示了完成此操作的更好的方式。
清单 16. 调用系列函数的更好的方式
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 中的代码进行扩展。这可能是一个需要您自行完成的一个练习,现在您的兴趣被点燃了吧。
回页首
结束语
在许多领域中都可以应用 JavaScript 中的函数式编程,以优美的方式完成日常活动。这篇文章中的示例只介绍了几种情况。如果您找到了函数式编程的合适场景,并应用这些概念,那么您就会有更多的理解,并且可以增加您的代码优美程度。
更多来自
D3.js 比例尺与坐标轴的配置详解
2026-11-05 16:54:46
JavaScript怎么遍历对象的Symbol键和不可枚举属性
2026-11-05 13:13:39
封装一个健壮的JS字符串判空工具函数
2026-11-05 12:16:14
JavaScript合并数组的三种常用方法:concat、扩展运算符与push
2026-11-05 11:22:29
JavaScript立即执行函数(IIFE)是什么及基本语法
2026-11-05 10:51:19
