函数式编程:声明式编程的优雅之道
什么是函数式编程
回想一下在餐厅点餐的场景。你不会走进厨房告诉厨师"先把水烧开, 然后放入面条, 煮 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'] (新数组)纯函数的优势:
- 可预测性: 相同输入总是得到相同输出
- 可测试性: 不需要设置复杂的环境
- 可缓存性: 可以缓存函数结果
- 并发安全: 不会产生竞态条件
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;
}
}函数式编程的优势
- 可预测性: 纯函数和不可变性使代码行为可预测
- 可测试性: 纯函数易于测试, 不需要复杂的设置
- 可组合性: 小函数可以组合成复杂功能
- 并发安全: 不可变数据消除了竞态条件
- 易于调试: 数据流清晰, 容易追踪
- 代码复用: 通用函数可以在多处使用
实践建议
- 优先使用纯函数: 将副作用隔离到边界
- 保持数据不可变: 使用扩展运算符和非变异方法
- 使用高阶函数: 利用
map、filter、reduce - 函数组合: 将复杂逻辑分解为小函数
- 避免循环: 用递归或数组方法替代
- 声明式风格: 关注"做什么"而非"怎么做"
小结
函数式编程是一种强大的编程范式, 它通过:
- 纯函数: 确保代码可预测和可测试
- 不可变性: 消除意外的状态变化
- 函数组合: 构建复杂的数据转换流程
- 声明式风格: 让代码更清晰易懂
虽然 JavaScript 不是纯粹的函数式语言, 但它完全支持函数式编程风格。你可以根据实际需求, 灵活组合函数式和面向对象两种范式, 构建既优雅又实用的代码。