JS轻量级函数式编程:从函数本质到高阶应用

作者:异步漫步时间:2026-10-25 12:21:46浏览:522

本文深入解析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的资料请关注本站其它相关文章!

作者最新文章