函数式编程

函数式编程核心概念(纯函数/不可变数据/一等公民)

#tech / dev / pattern #type / concept #status / growing

函数式编程

一句话定义

函数式编程 (FP) 是一种编程范式,将计算视为函数的组合,强调纯函数、不可变数据、避免副作用。

核心机制

1. 纯函数

输入相同则输出必然相同,且不产生副作用。

// 纯函数
function add(a, b) {
  return a + b;
}

// 非纯函数 — 依赖外部状态
let discount = 0.1;
function getPrice(price) {
  return price * (1 - discount); // discount 可能被外部修改
}

// 非纯函数 — 有副作用
function addToList(list, item) {
  list.push(item); // 修改了输入
  return list;
}

// 纯化版本
function addToPure(list, item) {
  return [...list, item]; // 返回新数组
}

2. 不可变数据

数据创建后不修改,通过函数生成新数据。

const user = { name: 'Alice', age: 25 };

// 错误:直接修改
user.age = 26;

// 正确:生成新对象
const updated = { ...user, age: 26 };

// 数组不可变
const nums = [1, 2, 3];
const added = [...nums, 4];         // 而非 nums.push(4)
const filtered = nums.filter(n => n > 1); // 而非 nums.splice(...)

3. 函数是一等公民

函数可以赋值给变量、作为参数传递、作为返回值。

const greet = (name) => `Hello, ${name}`;
const apply = (fn, value) => fn(value);
apply(greet, 'Alice'); // "Hello, Alice"

map / filter / reduce

这三者是 FP 在 JavaScript 中最常用的体现:

const orders = [
  { product: 'A', price: 100, qty: 2 },
  { product: 'B', price: 50, qty: 3 },
  { product: 'C', price: 200, qty: 1 },
];

// 链式调用 = 函数组合
const total = orders
  .filter(o => o.price > 60)          // 筛选
  .map(o => o.price * o.qty)          // 转换
  .reduce((sum, v) => sum + v, 0);    // 聚合
// 200 + 200 = 400

函数组合 (Composition)

// pipe:从左到右执行
const pipe = (...fns) => (x) => fns.reduce((acc, fn) => fn(acc), x);

const trim = (s) => s.trim();
const lower = (s) => s.toLowerCase();
const split = (sep) => (s) => s.split(sep);

const process = pipe(trim, lower, split(' '));
process('  Hello World  '); // ['hello', 'world']

// compose(从右到左,数学惯例)
const compose = (...fns) => (x) => fns.reduceRight((acc, fn) => fn(acc), x);

柯里化 (Currying)

// 普通函数
function add(a, b) { return a + b; }
add(1, 2); // 3

// 柯里化
function curry(fn) {
  return function curried(...args) {
    if (args.length >= fn.length) {
      return fn.apply(this, args);
    }
    return (...moreArgs) => curried(...args, ...moreArgs);
  };
}

const curriedAdd = curry(add);
curriedAdd(1)(2);     // 3
curriedAdd(1, 2);     // 3

// 实用场景:创建专用函数
const add10 = curriedAdd(10);
add10(5);  // 15
add10(20); // 30

常用术语

术语定义
纯函数相同输入 → 相同输出,无副作用
副作用修改外部状态(DOM、全局变量、IO)
高阶函数接收或返回函数的函数(map、filter、compose)
柯里化将多参数函数转为链式单参数调用
不可变性数据创建后不修改,变更时生成新副本
组合将小函数拼装成复杂逻辑

何时 FP 有帮助 vs 拖后腿

有帮助

  • 状态管理(Redux / Vuex 的 reducer 必须是纯函数)
  • 数据转换管道(ETL、API 数据映射)
  • 测试(纯函数天然易测,无需 mock)
  • 并发安全(不可变数据没有竞态问题)

拖后腿

  • 性能敏感路径(频繁创建新对象有 GC 压力)
  • IO 密集型代码(数据库、文件操作本质上是副作用)
  • 过度抽象(满屏 compose/pipe/curry 降低可读性)
  • 团队不熟悉时(FP 术语增加认知负担)

边界与常见误解

  • FP 不是”不能有副作用”:而是将副作用推到边界(IO 层),核心逻辑保持纯。
  • 不可变不是”完全不改”:而是不修改已存在的数据,用新数据代替。Immer 等库用 Proxy 实现写时复制。
  • forEach 不是 FPforEach 返回 undefined,强调的是执行副作用;map / filter / reduce 才是 FP 风格。
创建于 2025/1/1 更新于 2026/5/27