JavaScript 函数式编程核心:纯函数、柯里化与组合函数详解

作者:青柠时间:2026-10-27 00:49:41浏览:596

本文深入解析 JavaScript 函数式编程的三大核心概念:纯函数、柯里化与组合函数。通过对比 slice 与 splice 等实例阐明纯函数无副作用的特性,详解柯里化如何将多参数函数转化为单参数链式调用以实现参数复用,并演示如何构建组合函数以简化复杂逻辑,帮助开发者掌握函数式编程技巧。

前言

函数式编程(Functional Programming),也被称为泛函编程,是一种重要的编程范式。

尽管函数式编程的概念提出已久,且长期处于面向对象编程的主导之下,但近年来它重新受到关注。鉴于 JavaScript 将函数视为第一公民,它天然支持这种编程范式。

接下来,我们将重点探讨 JavaScript 函数式编程中的三个核心概念:纯函数、柯里化以及组合函数。

纯函数

纯函数的概念

根据维基百科的定义,在程序设计中,若函数满足特定条件,则被称为纯函数。

  • 1. 当输入值相同时,函数必须产生相同的输出;
  • 2. 函数的行为不依赖于隐藏信息或外部状态,也不受 I/O 设备产生的外部输出影响;
  • 3. 该函数不得产生任何可观察的副作用,例如触发事件、输出到设备或修改非返回值的外部对象。

综上所述,纯函数的特征可总结为:

  • 1. 相同输入始终对应相同输出;
  • 2. 执行过程中不产生任何可观察的副作用;
  • 3. 不依赖外部环境状态。

副作用

前文提到了“副作用”这一概念,具体含义如下:

  • 在医学语境中,副作用通常指药物产生的不良反应;
  • 而在计算机科学中,副作用指的是函数执行时,除了返回预期结果外,还产生了附加影响,例如修改全局变量状态、改变传入参数或产生其他输出。

纯函数案例

  • 编写一个求和函数 sum,只要输入固定值,它便返回固定结果且不产生任何副作用。
function sum(a, b) {
 return a + b
}

const res = sum(10, 20)
console.log(res) // 30
  • 以下 sum 函数虽然对固定输入返回固定输出,但内部修改了全局变量 message,因此被视为产生副作用,不属于纯函数。
let message = 'hello'
function sum(a, b) {
 message = 'hi'
 return a + b
}
  • JavaScript 提供了许多内置方法,其中部分为纯函数,部分则不是。例如数组操作中的 slice 和 splice 方法。

1. slice 方法是纯函数,因为对同一数组进行固定输入时,能得到固定输出且无副作用;

const nums = [1, 2, 3, 4, 5]
const newNums = nums.slice(1, 3)
console.log(newNums) // [2, 3]
console.log(nums) // [ 1, 2, 3, 4, 5 ]

2. splice 方法不是纯函数,因为它会修改原数组 nums。

const nums = [1, 2, 3, 4, 5]
const newNums = nums.splice(1, 3)
console.log(newNums) // [ 2, 3, 4 ]
console.log(nums) // [ 1, 5 ]

柯里化

柯里化的概念

维基百科对柯里化的定义如下:

  • 柯里化是将接收多个参数的函数转换为接收单一参数(原函数的第一个参数)的函数,并返回接收剩余参数及结果的新函数的技术;
  • 柯里化的核心思想是**“如果你固定某些参数,你将得到接受余下参数的一个函数”**;

总结来说,柯里化是指只传递部分参数调用函数,使其返回一个处理剩余参数的函数的过程。

函数柯里化的过程

首先编写一个普通的三参数求和函数:

function sum(x, y, z) {
 return x + y + z
}
const res = sum(10, 20, 30)
console.log(res) // 60

将上述求和函数进行柯里化改造后如下:

  • 将传入的三个参数拆解,依次返回一个函数,每次只传入一个参数;
  • 在保持功能不变的前提下,函数的调用方式发生了改变;
  • 注意:参数拆解不必严格拆分为单个,也可以采用 2+1 或 1+2 的组合方式;
function sum(x) {
 return function(y) {
 return function(z) {
 return x + y + z
 }
 }
}
const res = sum(10)(20)(30)
console.log(res)

使用 ES6 箭头函数可简写为:

const sum = x => y => z => x + y + z

函数柯里化的特点及应用

  • **使函数职责更加单一。**柯里化能让一个函数专注于处理单一问题,避免将大量逻辑堆积在一个函数中。

1. 若在上述三参数求和函数中增加需求:在计算前给每个值加 2,先展示不使用柯里化的实现效果:

function sum(x, y, z) {
 x = x + 2
 y = y + 2
 z = z + 2
 return x + y + z
}

2. 使用柯里化的实现效果:

function sum(x) {
 x = x + 2
 return function(y) {
 y = y + 2
 return function(z) {
 z = z + 2
 return x + y + z
 }
 }
}

3. 显然,柯里化后对每个参数的处理更加独立和单一。

  • **提高函数参数逻辑复用。**继续使用上述求和函数,增加需求:固定第一个参数为 10。直接查看柯里化的实现效果,后续若第一个参数始终为 10,可直接调用 sum10 函数。
function sum(x) {
 return function(y) {
 return function(z) {
 return x + y + z
 }
 }
}
const sum10 = sum(10) // 指定第一个参数值为10的函数
const res = sum10(20)(30)
console.log(res) // 60

自动柯里化函数的实现

function autoCurrying(fn) {
 // 1.拿到当前需要柯里化函数的参数个数
 const fnLen = fn.length

 // 2.定义一个柯里化之后的函数
 function curried\_1(...args1) {
 // 2.1拿到当前传入参数的个数
 const argsLen = args1.length

 // 2.1.将当前传入参数个数和fn需要的参数个数进行比较
 if (argsLen >= fnLen) {
 // 如果当前传入的参数个数已经大于等于fn需要的参数个数
 // 直接执行fn,并在执行时绑定this,并将对应的参数数组传入
 return fn.apply(this, args1)
 } else {
 // 如果传入的参数不够,说明需要继续返回函数来接收参数
 function curried\_2(...args2) {
 // 将参数进行合并,递归调用curried\_1,直到参数达到fn需要的参数个数
 return curried\_1.apply(this, [...args1, ...args2])
 }

 // 返回继续接收参数函数
 return curried\_2
 }
 }

 // 3.将柯里化的函数返回
 return curried\_1
}

测试代码:

function sum(x, y, z) {
 return x + y + z
}

const curryingSum = autoCurrying(sum)

const res1 = curryingSum(10)(20)(30)
const res2 = curryingSum(10, 20)(30)
const res3 = curryingSum(10)(20, 30)
const res4 = curryingSum(10, 20, 30)
console.log(res1) // 60
console.log(res2) // 60
console.log(res3) // 60
console.log(res4) // 60

组合函数

**组合函数(Compose Function)**是 JavaScript 开发中对函数使用的一种技巧或模式。对某一数据依次执行两个函数时,需要将这两个函数组合起来自动依次调用,这个过程称为函数组合,形成的函数即为组合函数。

需求:对一个数字先进行乘法运算,再进行平方运算。

  • 通常情况下,需先定义两个函数,然后依次调用:
function double(num) {
 return num * 2
}
function square(num) {
 return num ** 2
}
const duobleResult = double(10)
const squareResult = square(duobleResult)
console.log(squareResult) // 400

实现一个组合函数,将 double 和 square 两个函数组合起来:

function composeFn(fn1, fn2) {
 return function(num) {
 return fn2(fn1(num))
 }
}
const execFn = composeFn(double, square)
const res = execFn(10)
console.log(res) // 400

实现一个自动组合函数的函数:

function autoComposeFn(...fns) {
 // 1.拿到需要组合的函数个数
 const fnsLen = fns.length

 // 2.对传入的函数进行边界判断,所有参数必须为函数
 for (let i = 0; i < fnsLen; i++) {
 if (typeof fns[i] !== 'function') {
 throw TypeError('The argument passed must be a function.')
 }
 }

 // 3.定义一个组合之后的函数
 function composeFn(...args) {
 // 3.1.拿到第一个函数的返回值
 let result = fns[0].apply(this, args)

 // 3.1.判断传入的函数个数
 if (fnsLen === 1) {
 // 如果传入的函数个数为一个,直接将结果返回
 return result
 } else {
 // 如果传入的函数个数 >= 2
 // 依次将函数取出进行调用,将上一个函数的返回值作为参数传给下一个函数
 // 从第二个函数开始遍历
 for (let i = 1; i < fnsLen; i++) {
 result = fns[i].call(this, result)
 }

 // 将结果返回
 return result
 }
 }

 // 4.将组合之后的函数返回
 return composeFn
}

测试代码:

function double(num) {
 return num * 2
}
function square(num) {
 return num ** 2 
}

const composeFn = autoComposeFn(double, square)
const res = composeFn(10)
console.log(res) // 400

至此,关于 JavaScript 函数式编程中纯函数、柯里化以及组合函数的介绍结束。如需了解更多相关内容,请搜索本站往期文章或继续浏览后续文章,感谢大家的支持!

作者最新文章