Skip to content

函数式编程:声明式编程的优雅之道

什么是函数式编程

回想一下在餐厅点餐的场景。你不会走进厨房告诉厨师"先把水烧开, 然后放入面条, 煮 3 分钟, 加入调料..."相反, 你只需要说"我要一碗牛肉面"。这就是声明式思维——你声明你想要什么, 而不是详细说明如何做。

函数式编程(Functional Programming, FP)就是这种声明式编程范式。它强调:

  • 做什么(What)而不是怎么做(How)
  • 数据转换而不是状态修改
  • 函数组合而不是命令序列

与面向对象编程关注"对象及其交互"不同, 函数式编程关注"数据及其转换"。

函数式编程的核心概念

1. 纯函数(Pure Functions)

纯函数就像数学函数:相同的输入总是产生相同的输出, 并且没有副作用。

javascript
// ❌ 非纯函数 - 依赖外部状态
let taxRate = 0.1;

function calculateTax(price) {
  return price * taxRate; // 依赖外部变量
}

taxRate = 0.2; // 修改外部变量会影响函数结果
calculateTax(100); // 20 (结果不确定)

// ❌ 非纯函数 - 有副作用
function addToCart(item) {
  cart.push(item); // 修改外部状态
  console.log("Added:", item); // 产生副作用(I/O)
  return cart;
}

// ✅ 纯函数 - 无副作用, 结果可预测
function calculateTaxPure(price, taxRate) {
  return price * taxRate;
}

// 总是返回相同的结果
calculateTaxPure(100, 0.1); // 10
calculateTaxPure(100, 0.1); // 10

// ✅ 纯函数 - 不修改输入
function addToCartPure(cart, item) {
  // 返回新数组, 不修改原数组
  return [...cart, item];
}

const cart = ["Apple", "Banana"];
const newCart = addToCartPure(cart, "Orange");

console.log(cart); // ['Apple',  'Banana'] (未被修改)
console.log(newCart); // ['Apple',  'Banana',  'Orange'] (新数组)

纯函数的优势:

  1. 可预测性: 相同输入总是得到相同输出
  2. 可测试性: 不需要设置复杂的环境
  3. 可缓存性: 可以缓存函数结果
  4. 并发安全: 不会产生竞态条件
javascript
// 利用纯函数实现缓存(记忆化)
function memoize(fn) {
  const cache = new Map();

  return function (...args) {
    const key = JSON.stringify(args);

    if (cache.has(key)) {
      console.log("从缓存返回");
      return cache.get(key);
    }

    console.log("计算新结果");
    const result = fn(...args);
    cache.set(key, result);

    return result;
  };
}

// 纯函数 - 理想的缓存目标
const expensiveCalculation = (n) => {
  let result = 0;
  for (let i = 0; i < n; i++) {
    result += i;
  }
  return result;
};

const memoizedCalc = memoize(expensiveCalculation);

memoizedCalc(1000000); // 计算新结果
memoizedCalc(1000000); // 从缓存返回 (即时返回!)
memoizedCalc(500000); // 计算新结果
memoizedCalc(1000000); // 从缓存返回

2. 不可变性(Immutability)

不可变性意味着数据一旦创建就不能被修改。要"改变"数据, 实际上是创建新的数据:

javascript
// ❌ 可变方式 - 修改原数组
const numbers = [1, 2, 3];
numbers.push(4); // 修改原数组
numbers[0] = 10; // 修改原数组
console.log(numbers); // [10,  2,  3,  4]

// ✅ 不可变方式 - 创建新数组
const originalNumbers = [1, 2, 3];
const withNewNumber = [...originalNumbers, 4];
const withModifiedFirst = [10, ...originalNumbers.slice(1)];

console.log(originalNumbers); // [1,  2,  3] (未被修改)
console.log(withNewNumber); // [1,  2,  3,  4]
console.log(withModifiedFirst); // [10,  2,  3]

实际应用:管理用户数据

javascript
// 不可变的数据操作
const createUser = (id, name, email) => ({
  id,
  name,
  email,
  createdAt: new Date(),
  status: "active",
});

const updateUserEmail = (user, newEmail) => ({
  ...user,
  email: newEmail,
  updatedAt: new Date(),
});

const deactivateUser = (user) => ({
  ...user,
  status: "inactive",
  deactivatedAt: new Date(),
});

const addRole = (user, role) => ({
  ...user,
  roles: [...(user.roles || []), role],
});

// 使用
const user = createUser(1, "John", "[email protected]");
console.log("Original:", user);

const userWithNewEmail = updateUserEmail(user, "[email protected]");
console.log("Updated:", userWithNewEmail);
console.log("Original unchanged:", user);

const userWithRole = addRole(userWithNewEmail, "admin");
console.log("With role:", userWithRole);

// 可以轻松追踪历史
const userHistory = [user, userWithNewEmail, userWithRole];

console.log("\nUser history:");
userHistory.forEach((version, index) => {
  console.log(`Version ${index + 1}:`, version);
});

3. 高阶函数(Higher-Order Functions)

高阶函数是接受函数作为参数或返回函数的函数:

javascript
// 接受函数作为参数
const numbers = [1, 2, 3, 4, 5];

// map,  filter,  reduce 都是高阶函数
const doubled = numbers.map((n) => n * 2);
const evens = numbers.filter((n) => n % 2 === 0);
const sum = numbers.reduce((acc, n) => acc + n, 0);

console.log("Doubled:", doubled); // [2,  4,  6,  8,  10]
console.log("Evens:", evens); // [2,  4]
console.log("Sum:", sum); // 15

// 返回函数的函数
const createMultiplier = (factor) => {
  return (number) => number * factor;
};

const double = createMultiplier(2);
const triple = createMultiplier(3);

console.log(double(5)); // 10
console.log(triple(5)); // 15

// 实用高阶函数:创建验证器
const createValidator = (predicate, errorMessage) => {
  return (value) => ({
    isValid: predicate(value),
    error: predicate(value) ? null : errorMessage,
  });
};

const isEmail = createValidator(
  (value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value),
  "请输入有效的邮箱地址"
);

const isMinLength = (min) =>
  createValidator((value) => value.length >= min, `最少需要 ${min} 个字符`);

const isPassword = isMinLength(8);

console.log(isEmail("[email protected]")); // { isValid: true,  error: null }
console.log(isEmail("invalid")); // { isValid: false,  error: '...' }
console.log(isPassword("short")); // { isValid: false,  error: '...' }
console.log(isPassword("longenough")); // { isValid: true,  error: null }

4. 函数组合(Function Composition)

将简单函数组合成复杂函数, 就像搭积木一样:

javascript
// 基础工具函数
const add = (a) => (b) => a + b;
const multiply = (a) => (b) => a * b;
const subtract = (a) => (b) => b - a;

// 通用组合函数
const compose =
  (...fns) =>
  (value) => {
    return fns.reduceRight((acc, fn) => fn(acc), value);
  };

const pipe =
  (...fns) =>
  (value) => {
    return fns.reduce((acc, fn) => fn(acc), value);
  };

// 使用 compose (从右到左)
const calculate1 = compose(
  add(10), // 3. 最后加 10
  multiply(3), // 2. 然后乘以 3
  add(5) // 1. 首先加 5
);

console.log(calculate1(2)); // ((2 + 5) * 3) + 10 = 31

// 使用 pipe (从左到右, 更直观)
const calculate2 = pipe(
  add(5), // 1. 首先加 5
  multiply(3), // 2. 然后乘以 3
  add(10) // 3. 最后加 10
);

console.log(calculate2(2)); // ((2 + 5) * 3) + 10 = 31

// 实际应用:数据处理管道
const users = [
  { name: "John", age: 25, active: true, score: 80 },
  { name: "Jane", age: 30, active: false, score: 95 },
  { name: "Bob", age: 35, active: true, score: 70 },
  { name: "Alice", age: 28, active: true, score: 90 },
];

// 构建数据处理管道
const filterActive = (users) => users.filter((u) => u.active);
const sortByScore = (users) => [...users].sort((a, b) => b.score - a.score);
const getNames = (users) => users.map((u) => u.name);
const formatList = (names) => names.join(",  ");

// 组合成完整的处理流程
const getTopActiveUsers = pipe(filterActive, sortByScore, getNames, formatList);

console.log(getTopActiveUsers(users));
// "Alice,  John,  Bob"

函数式编程实战

数据转换管道

javascript
// 电商订单处理示例
const orders = [
  {
    id: 1,
    customer: "John",
    items: [
      { name: "Laptop", price: 999, quantity: 1 },
      { name: "Mouse", price: 29, quantity: 2 },
    ],
    status: "pending",
  },
  {
    id: 2,
    customer: "Jane",
    items: [{ name: "Keyboard", price: 79, quantity: 1 }],
    status: "shipped",
  },
  {
    id: 3,
    customer: "Bob",
    items: [
      { name: "Monitor", price: 299, quantity: 2 },
      { name: "Cable", price: 15, quantity: 3 },
    ],
    status: "pending",
  },
];

// 纯函数工具集
const calcItemTotal = (item) => item.price * item.quantity;

const calcOrderTotal = (order) => ({
  ...order,
  total: order.items.reduce((sum, item) => sum + calcItemTotal(item), 0),
});

const filterByStatus = (status) => (orders) =>
  orders.filter((order) => order.status === status);

const addShippingFee = (fee) => (order) => ({
  ...order,
  total: order.total + fee,
  shippingFee: fee,
});

const addTax = (taxRate) => (order) => ({
  ...order,
  tax: order.total * taxRate,
  total: order.total * (1 + taxRate),
});

const formatCurrency = (amount) => `$${amount.toFixed(2)}`;

const createInvoice = (order) => ({
  orderId: order.id,
  customer: order.customer,
  items: order.items.length,
  subtotal: formatCurrency(
    order.total - (order.tax || 0) - (order.shippingFee || 0)
  ),
  shipping: formatCurrency(order.shippingFee || 0),
  tax: formatCurrency(order.tax || 0),
  total: formatCurrency(order.total),
});

// 构建处理流程
const processPendingOrders = pipe(
  filterByStatus("pending"),
  (orders) => orders.map(calcOrderTotal),
  (orders) => orders.map(addShippingFee(10)),
  (orders) => orders.map(addTax(0.1)),
  (orders) => orders.map(createInvoice)
);

const invoices = processPendingOrders(orders);

console.log("待处理订单发票:");
invoices.forEach((invoice) => {
  console.log(`\n订单 #${invoice.orderId} - ${invoice.customer}`);
  console.log(`商品数: ${invoice.items}`);
  console.log(`小计: ${invoice.subtotal}`);
  console.log(`运费: ${invoice.shipping}`);
  console.log(`税费: ${invoice.tax}`);
  console.log(`总计: ${invoice.total}`);
});

函数式错误处理

javascript
// Result 类型 - 函数式错误处理
class Result {
  constructor(value, error = null) {
    this.value = value;
    this.error = error;
    this.isSuccess = error === null;
  }

  static success(value) {
    return new Result(value);
  }

  static failure(error) {
    return new Result(null, error);
  }

  map(fn) {
    if (!this.isSuccess) {
      return this;
    }

    try {
      return Result.success(fn(this.value));
    } catch (error) {
      return Result.failure(error.message);
    }
  }

  flatMap(fn) {
    if (!this.isSuccess) {
      return this;
    }

    try {
      return fn(this.value);
    } catch (error) {
      return Result.failure(error.message);
    }
  }

  getOrElse(defaultValue) {
    return this.isSuccess ? this.value : defaultValue;
  }

  getOrThrow() {
    if (!this.isSuccess) {
      throw new Error(this.error);
    }
    return this.value;
  }
}

// 使用 Result 进行安全的数据处理
const parseJSON = (jsonString) => {
  try {
    return Result.success(JSON.parse(jsonString));
  } catch (error) {
    return Result.failure(`JSON 解析失败: ${error.message}`);
  }
};

const validateUser = (data) => {
  if (!data.name || !data.email) {
    return Result.failure("缺少必要字段");
  }
  if (!data.email.includes("@")) {
    return Result.failure("邮箱格式无效");
  }
  return Result.success(data);
};

const saveUser = (user) => {
  console.log("保存用户:", user);
  return Result.success({ ...user, id: Date.now() });
};

// 函数式管道 - 优雅的错误处理
const processUserData = (jsonString) => {
  return parseJSON(jsonString).flatMap(validateUser).flatMap(saveUser);
};

// 测试
const validJSON = '{"name":"John", "email":"[email protected]"}';
const invalidJSON = '{"name":"John"}'; // 缺少 email
const malformedJSON = "{invalid}";

console.log("\n有效数据:");
const result1 = processUserData(validJSON);
console.log(result1.isSuccess ? "成功!" : "失败:", result1.error);

console.log("\n无效数据:");
const result2 = processUserData(invalidJSON);
console.log(result2.isSuccess ? "成功!" : "失败:", result2.error);

console.log("\n格式错误:");
const result3 = processUserData(malformedJSON);
console.log(result3.isSuccess ? "成功!" : "失败:", result3.error);

函数式状态管理

javascript
// 不可变状态管理
const createStore = (initialState = {}) => {
  let state = initialState;
  const listeners = [];

  const getState = () => state;

  const setState = (updater) => {
    // 使用函数更新状态, 确保不可变性
    const newState = typeof updater === "function" ? updater(state) : updater;

    if (newState !== state) {
      state = newState;
      listeners.forEach((listener) => listener(state));
    }
  };

  const subscribe = (listener) => {
    listeners.push(listener);

    // 返回取消订阅函数
    return () => {
      const index = listeners.indexOf(listener);
      if (index > -1) {
        listeners.splice(index, 1);
      }
    };
  };

  return { getState, setState, subscribe };
};

// 状态更新函数(纯函数)
const addTodo = (text) => (state) => ({
  ...state,
  todos: [
    ...state.todos,
    {
      id: Date.now(),
      text,
      completed: false,
    },
  ],
});

const toggleTodo = (id) => (state) => ({
  ...state,
  todos: state.todos.map((todo) =>
    todo.id === id ? { ...todo, completed: !todo.completed } : todo
  ),
});

const removeTodo = (id) => (state) => ({
  ...state,
  todos: state.todos.filter((todo) => todo.id !== id),
});

const setFilter = (filter) => (state) => ({
  ...state,
  filter,
});

// 选择器(纯函数)
const getTodos = (state) => state.todos;

const getVisibleTodos = (state) => {
  const { todos, filter } = state;

  switch (filter) {
    case "active":
      return todos.filter((t) => !t.completed);
    case "completed":
      return todos.filter((t) => t.completed);
    default:
      return todos;
  }
};

const getStats = (state) => ({
  total: state.todos.length,
  active: state.todos.filter((t) => !t.completed).length,
  completed: state.todos.filter((t) => t.completed).length,
});

// 使用
const store = createStore({
  todos: [],
  filter: "all",
});

// 订阅状态变化
store.subscribe((state) => {
  console.log("\n当前状态:");
  console.log("可见待办:", getVisibleTodos(state));
  console.log("统计:", getStats(state));
});

// 执行操作
console.log("=== 添加待办 ===");
store.setState(addTodo("学习函数式编程"));
store.setState(addTodo("写代码"));
store.setState(addTodo("锻炼"));

console.log("\n=== 完成第一项 ===");
const firstTodoId = store.getState().todos[0].id;
store.setState(toggleTodo(firstTodoId));

console.log("\n=== 只看未完成 ===");
store.setState(setFilter("active"));

console.log("\n=== 只看已完成 ===");
store.setState(setFilter("completed"));

函数式编程 vs 面向对象编程

两种范式各有优势, 可以结合使用:

javascript
// OOP 方式
class ShoppingCartOOP {
  constructor() {
    this.items = [];
  }

  addItem(product, quantity) {
    const existing = this.items.find((item) => item.product.id === product.id);

    if (existing) {
      existing.quantity += quantity;
    } else {
      this.items.push({ product, quantity });
    }
  }

  removeItem(productId) {
    this.items = this.items.filter((item) => item.product.id !== productId);
  }

  getTotal() {
    return this.items.reduce(
      (sum, item) => sum + item.product.price * item.quantity,
      0
    );
  }
}

// FP 方式
const createCart = (items = []) => ({ items });

const addItem = (cart, product, quantity) => {
  const existing = cart.items.find((item) => item.product.id === product.id);

  return {
    ...cart,
    items: existing
      ? cart.items.map((item) =>
          item.product.id === product.id
            ? { ...item, quantity: item.quantity + quantity }
            : item
        )
      : [...cart.items, { product, quantity }],
  };
};

const removeItem = (cart, productId) => ({
  ...cart,
  items: cart.items.filter((item) => item.product.id !== productId),
});

const getTotal = (cart) =>
  cart.items.reduce((sum, item) => sum + item.product.price * item.quantity, 0);

// 组合两种范式
class ShoppingCartHybrid {
  #cart;

  constructor() {
    this.#cart = createCart();
  }

  addItem(product, quantity) {
    this.#cart = addItem(this.#cart, product, quantity);
    return this;
  }

  removeItem(productId) {
    this.#cart = removeItem(this.#cart, productId);
    return this;
  }

  getTotal() {
    return getTotal(this.#cart);
  }

  getState() {
    return this.#cart;
  }
}

函数式编程的优势

  1. 可预测性: 纯函数和不可变性使代码行为可预测
  2. 可测试性: 纯函数易于测试, 不需要复杂的设置
  3. 可组合性: 小函数可以组合成复杂功能
  4. 并发安全: 不可变数据消除了竞态条件
  5. 易于调试: 数据流清晰, 容易追踪
  6. 代码复用: 通用函数可以在多处使用

实践建议

  1. 优先使用纯函数: 将副作用隔离到边界
  2. 保持数据不可变: 使用扩展运算符和非变异方法
  3. 使用高阶函数: 利用 mapfilterreduce
  4. 函数组合: 将复杂逻辑分解为小函数
  5. 避免循环: 用递归或数组方法替代
  6. 声明式风格: 关注"做什么"而非"怎么做"

小结

函数式编程是一种强大的编程范式, 它通过:

  • 纯函数: 确保代码可预测和可测试
  • 不可变性: 消除意外的状态变化
  • 函数组合: 构建复杂的数据转换流程
  • 声明式风格: 让代码更清晰易懂

虽然 JavaScript 不是纯粹的函数式语言, 但它完全支持函数式编程风格。你可以根据实际需求, 灵活组合函数式和面向对象两种范式, 构建既优雅又实用的代码。