JavaScript函数式编程核心概念与实践指南

作者:闭包旅人

时间:2026-10-29

浏览:641

本文系统解析JavaScript中的函数式编程范式,涵盖一等公民函数、纯函数无副作用特性、不可变数据带来的性能与安全优势,以及递归、惰性求值和闭包等核心技术。通过对比命令式编程,展示map、filter、reduce等工具函数的实际应用,帮助开发者构建更简洁、可维护的代码。

文章目录

编程范式概述

编程范式

编程范式是由思考问题及实现愿景的工具所构成的框架。现代编程语言大多属于多范式(或多重范式)语言,它们同时支持多种编程风格,例如面向对象、元程序设计、函数式以及面向过程等。

编程范式概述

函数式编程范式

函数式编程核心概念

函数式编程常被比作氢燃料驱动汽车——技术先进且具未来感,但尚未大规模普及。与命令式编程不同,后者由一系列更新全局状态的语句组成;函数式编程则将计算转化为表达式求值。这些表达式完全由纯数学函数构成,这些数学函数作为“一等公民”,可像普通值一样被使用和处理,且不存在副作用。

编程范式概述

函数式编程高度重视以下原则: 函数是一等公民

我们应将函数视为与编程语言中其他对象同等的存在。具体而言,可以将函数存储在变量中,动态创建函数,或将函数作为返回值及参数传递给其他函数。以下通过示例说明:

 编程范式概述

字符串可以保存为变量,函数同样可以,例如:

代码如下:

var sayHello = function() { return “Hello” };

字符串可以保存为对象字段,函数同样可以,例如:

代码如下:

var person = {message: “Hello”, sayHello: function() { return “Hello” }};

字符串可以在需要时动态创建,函数同样可以,例如:

代码如下:

“Hello ” + (function() { return “World” })(); //=> Hello World

字符串可以作为输入参数传递给函数,函数同样可以:

代码如下:

function hellloWorld(hello, world) { return hello + world() }

字符串可以作为函数返回值,函数同样可以,例如:


return “Hello”;
return function() { return “Hello”};

高阶函数与纯函数

高阶案例

编程范式概述

若函数将其他函数作为输入参数或返回值,则称为高阶函数。前文已展示过一个高阶函数的例子。下面进一步探讨更复杂的情况。 例1:


[1, 2, 3].forEach(alert);
// alert 弹窗显示“1" 
// alert 弹窗显示 "2" 
// alert 弹窗显示 "3”

例2:


function splat(fun) {
  return function(array) {
    return fun.apply(null, array);
  };
}
var addArrayElements = splat(function(x, y) { return x + y });
addArrayElements([1, 2]);
//=> 3

最爱纯函数

编程范式概述

纯函数不产生任何副作用,所谓副作用是指函数对外部状态的修改。例如:

  • 修改某个变量
  • 修改数据结构
  • 对外界某个变量设置字段
  • 抛出例外或者弹出错误信息

最简单的例子是数学函数。Math.sqrt(4) 始终返回2,它不依赖任何外部信息(如状态或配置参数),且从不造成副作用。

不可变数据与递归

避免修改状态

编程范式概述

函数式编程推崇纯函数,这类函数不改变数据,因此常用于创建不可变数据。这种方式无需修改已有数据结构,而是高效地新建数据。 或许有人会问:如果一个纯函数通过改变局部数据来生成不可变的返回值,是否允许?答案是肯定的。 在JavaScript中,极少数据类型默认是不可变的。String 是一个典型例子:


  var s = "HelloWorld";
  s.toUpperCase();
  //=> "HELLOWORLD"
  s;
  //=> "HelloWorld"

不可改变状态的好处

• 避免混乱并提高程序准确性:在复杂系统中,许多难以排查的Bug源于状态被程序中其他客户端代码意外修改。 • 确立“快速简洁”的多线程编程:若多线程可修改同一共享值,则必须同步获取值,这对专家而言也是繁琐且易错的挑战。 软件事务内存和Actor模型提供了在线程安全方式下处理修改的直接手段。

使用递归而非循环调用

编程范式概述

递归是最著名的函数式编程技术。若尚不熟悉,可理解为递归函数即调用自身的函数。 替代反复循环的经典方式是使用递归,即每次完成函数体操作后,继续处理集合中的下一项,直到满足结束条件。递归天然契合某些算法实现,如遍历树形结构(每个树枝都是一棵小树)。 在任何语言中,递归都是重要的函数式编程方式。许多函数式语言甚至要求更严格:仅支持递归遍历,不支持显式循环遍历。这需要语言必须保证消除尾端调用,而JavaScript并不支持这一特性。

惰性求值与闭包

惰性求值优于激进计算

编程范式概述

数学定义了许多无穷集合,如自然数(所有正整数),它们仅以符号表示。任意特定有限子集都在需要时求值,这称为惰性求值(也称非严格求值、按需调用或延迟执行)。及早求值会强制表示所有无穷数据,这显然不可能。 许多语言默认采用惰性求值,部分语言还提供惰性数据结构以表达无穷集合,并在需要时进行精确计算。 显然,一行代码 result = compute() 表示将 compute() 的返回结果赋值给 result,但 result 的值究竟是多少,只有在其被使用时才有意义。 可见,策略的选择能显著提升性能,特别是在链式处理或数组处理时。这些都是函数式程序员喜爱的编程技术。 这开创了许多可能性,包括并发执行、并行技术及合成。 但存在一个问题:JavaScript 自身并不进行惰性求值。尽管如此,JavaScript 中的函数库可以有效模拟惰性求值。 闭包的全部好处

所有函数式语言都具备闭包,然而这一语言特性常被讨论得较为神秘。闭包是一个函数,它对内部引用的所有变量具有隐式绑定。换句话说,该函数对其引用的变量封闭了一个上下文。JavaScript 中的闭包是指能够访问父级作用域的函数,即使父级函数已经调用完毕。


  function multiplier(factor) {
   return function(number) {
     return number * factor;
   };
  }
 var twiceOf = multiplier(2);
  console.log(twiceOf(6));
//=> 12

声明式编程与工具库

声明式优于命令式编程

函数式编程是声明式的,类似于数学运算,属性和关系是预先定义好的。运行时知道如何计算最终结果。阶乘函数的定义提供了一个例子: factorial(n) = 1 if n = 1 n * factorial(n-1) if n > 1 该定义将 factorial(n) 的值关联到 factorial(n-1),属于递归定义。特殊情况下的 factorial(1) 终止了递归。


var imperativeFactorial = function(n) {
  if(n == 1) {
    return 1
  } else {
    product = 1;
    for(i = 1; i <= n; i++) {
       product *= i;
    }
    return product;
   }
}
var declarativeFactorial = function(n) {
    if(n == 1) {
       return 1
    } else {
       return n * factorial(n - 1);
   }
 }

从实现阶乘计算来看,声明式的阶乘可能看似“命令式”,但其结构更接近声明式。 命令式阶乘使用可变值、循环计数器和结果来累加计算后的结果。该方法显式地实现了特定算法。与声明式版本不同,这种方法包含许多可变步骤,导致更难理解且更易产生Bug。

编程范式概述

函数式JavaScript库

存在许多函数式库:underscore.js,lodash,Fantasy Land, Functional.js, Bilby.js, fn.js, Wu.js, Lazy.js, Bacon.js, sloth.js, stream.js, Sugar, Folktale, RxJs 等。 函数式程序员工具包

map(), filter(), 和 reduce() 函数构成了函数式程序员工具包的核心。纯高阶函数成为函数式方法的主力。事实上,它们是纯函数和高阶函数应效仿的典型。它们以函数为输入,返回无副作用的输出。 这些 JavaScript 函数对每位函数式程序员都至关重要。它们能去除循环和语句,使代码更加整洁。这些是 ECMAScript5.1 浏览器的标准,仅处理数组。每次调用都会创建并返回一个新数组,已存在的数组不会被修改。但需注意,它们还将函数作为输入参数,通常作为回调的匿名函数。它们会遍历整个数组并将该回调函数应用于每一项! myArray = [1,2,3,4]; newArray = myArray.map(function(x) {return x*2}); console.log(myArray); // Output: [1,2,3,4] console.log(newArray); // Output: [2,4,6,8] 除这三个函数外,还有许多函数可嵌入几乎每一个函数式应用中: forEach(),concat(), reverse(), sort(), every() 以及some(). JavaScript的聚范式

JavaScript 当然不是严格意义上的函数式编程语言,这也促使了对其他范式的使用: 命令式编程:基于详细操作描述的编程 基于原型的面向对象式编程:基于原型对象及其实例的编程 元程序编程:操纵JavaScript执行模型的编程方式。对元程序编程的一个很好的定义描述为“编程发生在您书写代码做某事的时候,而元程序编程则发生在您书写代码导致某事的解释方式发生的时候。”

以上就是JavaScript函数式编程的使用方法,希望对大家的学习有所帮助。

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章