什么是函数柯里化

65 阅读4分钟

1. 函数式编程的基础

1.1 什么是函数式编程?

函数式编程是一种编程范式,强调将计算过程建模为数学函数的求值,避免状态变化和可变数据。其核心思想包括:

  • 纯函数:函数的输出仅依赖于输入,且无副作用。
  • 不可变性:数据一旦创建不可修改,变化通过创建新数据实现。
  • 高阶函数:函数可以作为参数传递或作为返回值返回。
  • 声明式:关注“做什么”而非“怎么做”,代码更简洁。

在 JavaScript 中,函数是一等公民(First-Class Citizen),支持高阶函数、闭包等特性,使其天然适合函数式编程。

1.2 函数式编程的核心原则

  • 纯函数:相同的输入始终产生相同的输出,无副作用。例如:

    function add(a, b) {
      return a + b;
    }
    console.log(add(2, 3)); // 5
    

    反例(非纯函数):

    let total = 0;
    function addToTotal(value) {
      total += value;
      return total;
    }
    
  • 不可变性:避免直接修改数据:

    const numbers = [1, 2, 3];
    const doubled = numbers.map(n => n * 2); // [2, 4, 6]
    console.log(numbers); // [1, 2, 3](原数组未变)
    
  • 避免副作用:函数不应修改外部状态:

    let total = 0; // 外部状态 
    function addToTotal(num) { 
      total += num; // 修改了外部状态 total 
      return total; 
    }
    
  • 函数组合:通过组合小函数实现复杂逻辑:

    const compose = (f, g) => x => f(g(x));
    const addOne = x => x + 1;
    const double = x => x * 2;
    const addOneThenDouble = compose(double, addOne);
    console.log(addOneThenDouble(5)); // 12
    

1.3 为什么在 JavaScript 中使用函数式编程?

  • 可预测性:纯函数和不可变性降低调试难度。
  • 模块化:高阶函数和函数组合提升代码复用性。
  • 并发友好:无状态操作更适合异步和并发场景。
  • 现代框架支持:React、Redux 等框架大量采用函数式思想。

2. 核心函数式编程概念

2.1 纯函数与副作用

纯函数是函数式编程的基石。实现纯函数需遵循:

  • 输入决定输出:不依赖外部变量。
  • 无外部修改:不更改全局状态或 DOM。

避免副作用:

// 非纯函数
let counter = 0;
function increment() {
  counter++;
  return counter;
}

// 纯函数
function incrementCounter(current) {
  return current + 1;
}

2.2 高阶函数

高阶函数接受函数作为参数或返回函数。JavaScript 的数组方法(如 mapfilterreduce)是典型的高阶函数。

const numbers = [1, 2, 3, 4];
const doubled = numbers.map(n => n * 2); // [2, 4, 6, 8]
const evens = numbers.filter(n => n % 2 === 0); // [2, 4]
const sum = numbers.reduce((acc, n) => acc + n, 0); // 10

2.3 闭包与函数式编程

闭包允许函数访问其定义时的词法作用域,是实现函数式编程的重要机制。

function createCounter() {
  let count = 0;
  return {
    increment: () => ++count,
    get: () => count,
  };
}

const counter = createCounter();
console.log(counter.increment()); // 1
console.log(counter.get()); // 1

闭包实现私有状态:

function createUser(name) {
  let _name = name;
  return {
    getName: () => _name,
    setName: newName => (_name = newName),
  };
}

const user = createUser('Alice');
console.log(user.getName()); // Alice
user.setName('Bob');
console.log(user.getName()); // Bob

2.4 不可变性

JavaScript 中的数组和对象是可变的,需通过复制实现不可变性。

数组不可变操作:

const numbers = [1, 2, 3];
const newNumbers = [...numbers, 4]; // [1, 2, 3, 4]
console.log(numbers); // [1, 2, 3]

对象不可变操作:

const user = { name: 'Alice', age: 30 };
const updatedUser = { ...user, age: 31 };
console.log(user); // { name: 'Alice', age: 30 }
console.log(updatedUser); // { name: 'Alice', age: 31 }

使用 Object.freeze

const config = Object.freeze({
  apiUrl: 'https://api.example.com',
  timeout: 5000,
});
config.apiUrl = 'new-url'; // 无效果
console.log(config.apiUrl); // https://api.example.com

什么是柯里化

将一个多元(多参数)函数,转换成一个可以依次调用的单元函数

image.png

这里调用curryAdd后,返回的函数就通过闭包的形式记住了curryAdd的第一个参数

封装柯里化函数

下面封装一个curry函数

function curry(fn){
    return function curriedFn(){
      var args = Array.prototype.slice.call(arguments) 
      //将 arguments 转换为一个真正的数组,方便后面的concat操作
      if (args.length < fn.length){ //参数没传完
        return function (){
          var args2 = Array.prototype.slice.call(arguments) 
          return curriedFn(...args.concat(args2)) 
          //数组合并后展开,否则传入的是一个数组参数
        }
      } else { //参数传完了
        return fn(...args)
      }
    }
  }

封装好后,测试使用

const add = (x,y,z) =>{
    console.log(x,y,z);
  }
  var _add = curry(add) // 分批调用
  var a = _add(1)
  var b = a(2)
  var c = b(3)
  // 1 2 3 

  _add(1)(2)(3) // 一次调用
  // 1 2 3

柯里化的用途

总的来说,就是实现参数的复用

  1. 表单的校验
function checkByRegExp(reg, string){ 
  return reg.test(string) 
} 
// 校验手机号,regExp是重复的,很麻烦 
checkByRegExp(/^\d{12}$/, '15152525634'); 
checkByRegExp(/^\d{12}$/, '13456574566'); 
checkByRegExp(/^\d{12}$/, '18123787385'); 

var curried = curry(checkByRegExp) 
var checkPhone = curried(/^\d{12}$/) checkPhone('18848272720')
  1. 网络请求
// 假设一个通用的请求 API 
const request = (type, url, options) => ... 
// GET 请求 
request('GET', 'http://....') 
// POST 请求 request('POST', 'http://....') 
// 但是通过部分调用后,我们可以抽出特定 type 的 request 
const get = request('GET'); 
get('http://', {..})

这里转载了这篇文章

JavaScript 函数式编程思想与柯里化的深度剖析JavaScript 作为一门多范式语言,既支持面向对象编程,也为 - 掘金