JavaScript函数式编程核心概念:纯函数、副作用与不可变性

作者:半夏时间:2026-10-23 21:01:01浏览:481

本文详解JavaScript中的函数式编程范式,通过纯函数、副作用控制及不可变性三大核心概念,展示如何将命令式代码重构为高复用、低耦合的结构。内容涵盖高阶函数拆分、依赖注入及深拷贝技巧,旨在提升代码稳定性与可测试性。

函数式编程

函数式编程是一种构建软件的结构风格,它将计算过程视为对数学函数的求值,核心在于避免状态变更和数据可变。这与命令式编程形成鲜明对比。例如,若需实现数组元素加一的需求:


// 数组每个数字加一, 命令式编程
let arr = [1, 2, 3, 4];
let newArr = [];
for(let i = 0; i < arr.length; i++){
 newArr.push(arr[i] + 1);
}

console.log(newArr); // [2, 3, 4, 5]

上述命令式代码虽能正确运行,但缺乏复用性。我们可以转换思路,将操作拆解为两个独立部分:遍历数组和执行具体逻辑。将这两者分离后:


// 先拆加一出来
let add1 = x => x +1;

// 然后拆遍历方法出来,通过遍历返回一个操作后的新数组
// fn是我们需要对每个数组想进行的操作
let createArr = (arr, fn) => {
 const newArr = [];
 for(let i = 0; i < arr.length; i++){
 newArr.push(fn(arr[i]));
 }

 return newArr;
} 

// 用这两个方法来得到我们期望的结果
const arr = [1, 2, 3, 4];
const newArr = createArr(arr, add1);
console.log(newArr); // [2, 3, 4, 5], 结果仍然是对的

拆分后,若后续需求变为将数组元素乘以2,只需新增乘法逻辑并复用遍历代码即可:


let multiply2 = x => x * 2;

// 调用之前的createArr
const arr2 = [1, 2, 3, 4];
const newArr2 = createArr(arr2, multiply2);
console.log(newArr2); // [2, 4, 6, 8], 结果是对的

然而,上述加一函数仍不够通用,复用性有限。我们可以进一步将其拆解:


// 先写一个通用加法,他接收第一个加数,返回一个方法
// 返回的这个方法接收第二个加数,第一个加数是上层方法的a
// 这样当我们需要计算1+2是,就是add(1)(2)
let add = (a) => {
 return (b) => {
 return a + b;
 }
}

// 我们也可以将返回的函数赋给一个变量,这个变量也就变成一个能特定加a的一个方法
let add1 = add(1);

let res = add1(4); 
console.log(res); // 5

由此可见,函数式编程的本质是将程序分解为更高复用、更可靠且更易理解的模块,再通过组合这些模块构建出逻辑清晰的程序整体。

纯函数

纯函数指在相同输入下始终返回相同结果的函数。它不依赖外部状态或数据变化,仅由输入参数决定输出,且不会修改外部数据,仅通过返回值与外界交互。以下函数因依赖外部变量discount,故不属于纯函数:


let discount = 0.8;
const calPrice = price => price * discount;
let price = calPrice(200); // 160

// 当discount变了,calPrice传同样额参数,结果不一样,所以不纯
discount = 0.9;
price = calPrice(200); // 180

将其改造为纯函数十分简单,只需将discount作为参数传入:

const calPrice = (price, discount) => price * discount;

纯函数能确保代码稳定性,因为相同输入必然对应相同输出。反之,不纯的函数可能引入副作用。

函数副作用

函数副作用指除返回值外,对主调用方产生额外影响的行为,如修改全局变量、外部变量或参数。这会导致难以追踪的Bug并降低代码可读性。例如,以下foo函数存在副作用,若后续代码依赖变量a,可能获取到被污染的值:


let a = 5;
let foo = () => a = a * 10;
foo();
console.log(a); // 50

除了自定义函数,部分原生API也可能产生副作用,编码时需特别注意:

函数式编程

我们的目标是尽可能减少副作用,将函数编写为纯函数。以下函数因依赖new Date(),每次执行结果不同,故不纯:

函数式编程

要将其改为纯函数,可采用依赖注入方式,将不纯的部分提取为参数。这样可将副作用代码集中处理于外部,远离核心逻辑,从而保障核心代码的稳定性:


// 依赖注入
const foo = (d, log, something) => {
 const dt = d.toISOString();
 return log(`${dt}: ${something}`);
}

const something = 'log content';
const d = new Date();
const log = console.log.bind(console);
foo(d, log, something);

因此,减少副作用的通用方法包括:

1. 函数仅通过参数进行运算,不修改参数本身 2. 函数内部不修改外部变量 3. 运算结果通过返回值返回给外部

可变性和不可变性

  • 可变性:指变量创建后允许被任意修改。
  • 不可变性:指变量创建后其值永远不会发生改变。不可变性是函数式编程的核心基石。

以下是一个可变操作的示例:

函数式编程

若必须修改参数,应先对该参数进行深拷贝,再对副本进行操作,从而避免修改原始参数:

函数式编程

至此,关于JavaScript函数式编程的详解已介绍完毕。如需了解更多JS函数式编程内容,请查阅本站往期文章或继续浏览后续相关教程,感谢大家的支持!

作者最新文章