本文深入解析JavaScript轻量级函数式编程的核心概念。从函数的数学映射本质出发,探讨显式与隐式输出的区别,强调避免副作用以构建纯函数。同时介绍高阶函数与闭包在参数传递中的应用,并对比具名函数与箭头函数的优劣,最后阐述丢弃this绑定的必要性,旨在帮助开发者编写更具可读性和可维护性的代码。
前言
推测读者可能阅读过
《medium 五万赞好文-《我永远不懂 JS 闭包》》
《“类”设计模式和“原型”设计模式——“复制”和“委托”的差异》
若接触过上述文章,你对 JavaScript 中【函数】的关注度可能会显著提升。
这种轻松的氛围下,可以确认:JavaScript 本质上是一种轻量级的函数式编程语言。
让我们拆解并细细品味这一观点。
本文将依据《JavaScript 轻量级函数式编程》一书,先解析函数式编程的核心落脚点,再探讨 JavaScript 为何被定义为“轻量级”。
这是《JS如何函数式编程》系列的首篇,欢迎点赞和关注以持续获取更新。
FP 概览
重要性
函数式编程(FP)并非新概念,它贯穿了编程历史。直到近年,它才成为开发界的主流理念。
函数式编程拥有完善且清晰的原则。掌握这些原则后,我们能更快速地阅读代码并定位问题,这正是函数式编程重要的原因。
例如,你可能编写过命令式代码,如使用 if 语句和 for 循环来精确指导计算机执行步骤。而声明式代码,以及遵循函数式编程原则编写的代码,则更专注于描述最终结果。
函数式编程提供了一种重新组织代码的方式,使数据流更加清晰,帮助读者迅速理解你的思路。
请记住,你编写的每一行代码都需要有人维护,这个人可能是团队成员,也可能是未来的你。
最初的函数
函数式编程不仅仅是使用 function 关键字,正如面向对象编程不仅仅是使用对象。
那么,函数的真正意义是什么?
回到起点,我们心中的函数应当如下所示:

f(x) = 2x² + 3,这是数学上的标准函数。它与函数式编程有何关联?
函数的本质是【映射】。它以优雅的方式描述一组值与另一组值之间的映射关系,即输入值与输出值之间的关联。
在编程中,函数可能有多个输入值,也可能没有;可能有一个输出值(return 值),也可能没有。
若计划使用函数式编程,应尽可能多地使用函数而非程序。所有编写的 function 都应接收输入值并返回输出值(原因将在后续章节逐一介绍)。
此处,输入值即函数传参,输出值即 return 的内容(若无 return 或仅使用 return;,则隐式返回 undefined)。
思考“return”
考虑以下代码:
function foo(x) {
if (x > 10) return x + 1;
var y = x / 2;
if (y > 3) {
if (x % 2 == 0) return x;
}
if (y > 1) return y;
return x;
}
foo(2) 返回什么? foo(4) 返回什么? foo(8), foo(12) 呢?
将上述代码变形,请思考:
function foo(x) {
var retValue;
if (retValue == undefined && x > 10) {
retValue = x + 1;
}
var y = x / 2;
if (y > 3) {
if (retValue == undefined && x % 2 == 0) {
retValue = x;
}
}
if (retValue == undefined && y > 1) {
retValue = y;
}
if (retValue == undefined) {
retValue = x;
}
return retValue;
}
这样写是否更容易理解?
在每个 retValue 可被设置的分支中,都有一个守护者确保 retValue 未被设置时才执行。
相比在函数中提前使用 return,我们更应使用常见的流控制(if 逻辑)来控制 retValue 的赋值,最后再 return retValue。
使用 return 来实现流控制,会创造更多的隐含意义。
再考虑以下代码:
// 通过一个函数修改变量 y 的值
var y;
function foo(x) {
y = (2 * Math.pow( x, 2 )) + 3;
}
foo( 2 );
y;
但我们也可以这样写:
function foo(x) {
return (2 * Math.pow( x, 2 )) + 3;
}
var y = foo( 2 );
y;
后一个版本更有优势吗?
答案是肯定的:有!
后一个版本中的 return 表示一个显式输出,而前者的 y 赋值是一个隐式输出。
通常,开发人员更喜欢显式模式而非隐式模式。
为何说后者 return 出来的是显式的,而前者的 y 赋值是隐式的?
这个例子给出了答案:
function sum(list) {
var total = 0;
for (let i = 0; i < list.length; i++) {
if (!list[i]) list[i] = 0; // list 使用了 nums 的引用,不是对 [1,3,9,..] 的值复制,而是引用复制。
total = total + list[i];
}
return total;
}
var nums = [ 1, 3, 9, 27, , 84 ];
sum( nums ); // 124
这段代码中,除了 return 的输出,是否还有其他输出可能改变函数外部的参数 nums 的值?
是的!在注释的一行中,我们无意中改变了 nums。
console.log(nums) // [1, 3, 9, 27, 0, 84]
JavaScript 对数组、对象和函数使用引用和引用复制,我们可以轻易地从函数中创建输出,即使是无意的。
这种隐式函数输出在函数式编程中有一个特殊名称:副作用。
没有副作用的函数也有一个特殊名称:纯函数,这一概念至关重要,后续将有更多讨论。
高阶函数
一个函数若能接受或返回一个或多个函数,则被称为高阶函数。
其中最强大的概念是:【闭包】。
我们在后续举例中将大量使用闭包,它可能是所有函数式编程中最重要的基础。
此处举一小例:
假设你需要将两个值相加,其中一个已知,另一个稍后才知道,你可以使用闭包来记录第一个输入值:
function makeAdder(x) {
return function sum(y){
return x + y;
};
}
//我们已经分别知道作为第一个输入的 10 和 37
var addTo10 = makeAdder( 10 );
var addTo37 = makeAdder( 37 );
// 紧接着,我们指定第二个参数
addTo10( 3 ); // 13
addTo10( 90 ); // 100
addTo37( 13 ); // 50
这种在连续函数调用中指定输入的方式,是函数式编程中非常普遍的形式。
它可分为两类:偏函数应用和柯里化,后续将展开讨论。
具名函数
我们提倡使用具名函数而非匿名函数,这更有利于代码语义化,例如 getPreferredName(..),操作意图明确,且能很好地回溯问题,避免显示 (anonymous function)。
但 => 箭头函数除外,箭头函数仍需有效利用。
=> 箭头函数的兴奋之处在于它几乎完全遵循函数的数学符号,特别是像 Haskell 这样的函数式编程语言。它能简化、优化代码片段中的空间。
丢弃“this”
JavaScript 中的 this 绑定规则确实难以记忆。好消息是,我们将丢弃 this,不去理会它。
这样做的原因是:this 是函数的一个隐式输入参数。前文提到,通常开发人员喜欢显式模式而非隐式模式,这种隐式输入违背了我们的原则。
阶段小结
函数是强大的!
- 我们学习函数式编程的全部理由是为了书写更具可读性的代码。
- 在程序中,函数不仅仅是一个语句或操作的集合,而是需要一个或多个输入(理想情况下只需一个!)和一个输出。开发人员喜欢显式输入输出而非隐式输入输出。
- 函数内部的函数可以取到闭包外部变量,并记住它们以备日后使用。这是所有程序设计中最重要的概念之一,也是函数式编程的基础。
- 要警惕匿名函数,特别是 => 箭头函数。虽然在编程时用起来很方便,但是会对增加代码阅读的负担。
- 别用 this 敏感的函数。这不需要理由。
以上就是JS函数式编程实现XDM一的详细内容,更多关于JS轻量级函数式编程XDM的资料请关注本站其它相关文章!