JavaScript深入之函数组合详解
函数组合是函数式编程中的一个核心概念,它允许你将多个函数组合成一个新的函数,使代码更加模块化、可读和可维护。
什么是函数组合?
函数组合是将多个简单函数组合成一个更复杂函数的过程。组合后的函数会按照从右到左(或从左到右)的顺序依次执行这些函数。
基本实现
1. 简单的 compose 函数
// 从右到左组合
const compose = (...fns) => (value) => fns.reduceRight((acc, fn) => fn(acc), value);// 从左到右组合(也称为 pipe)
const pipe = (...fns) => (value) => fns.reduce((acc, fn) => fn(acc), value);
2. 使用示例
// 一些简单的工具函数
const add = (x) => x + 2;
const multiply = (x) => x * 3;
const subtract = (x) => x - 5;
const square = (x) => x * x;// 组合函数:先执行 subtract,然后 multiply,最后 add
const composed = compose(add, multiply, subtract);
console.log(composed(10)); // 输出: 23
// 计算过程: 10 - 5 = 5, 5 * 3 = 15, 15 + 2 = 17// 管道函数:先执行 add,然后 multiply,最后 subtract
const piped = pipe(add, multiply, subtract);
console.log(piped(10)); // 输出: 31
// 计算过程: 10 + 2 = 12, 12 * 3 = 36, 36 - 5 = 31
实际应用场景
1. 数据处理管道
// 数据处理函数
const trim = (str) => str.trim();
const toLowerCase = (str) => str.toLowerCase();
const splitWords = (str) => str.split(' ');
const capitalizeFirst = (str) => str.charAt(0).toUpperCase() + str.slice(1);
const joinWithDash = (arr) => arr.join('-');// 组合成一个数据处理管道
const processString = pipe(trim,toLowerCase,splitWords,(words) => words.map(capitalizeFirst),joinWithDash
);console.log(processString(' HELLO world JavaScript '));
// 输出: "Hello-World-Javascript"
2. 用户数据验证和处理
// 验证函数
const validateEmail = (email) => {if (!email.includes('@')) throw new Error('Invalid email');return email;
};const validateAge = (age) => {if (age < 18) throw new Error('Must be 18 or older');return age;
};const sanitizeEmail = (email) => email.trim().toLowerCase();// 组合验证和处理函数
const validateUser = pipe(sanitizeEmail,validateEmail,(email) => ({ email, valid: true })
);try {const user = validateUser(' TEST@EXAMPLE.COM ');console.log(user); // 输出: { email: "test@example.com", valid: true }
} catch (error) {console.error(error.message);
}
高级用法
1. 带参数的函数组合
// 柯里化函数,便于组合
const curry = (fn) => {return function curried(...args) {return args.length >= fn.length ? fn.apply(null, args): (...moreArgs) => curried(...args, ...moreArgs);};
};// 柯里化示例函数
const add = curry((a, b) => a + b);
const multiply = curry((a, b) => a * b);// 组合带参数的函数
const addThenMultiply = compose(multiply(3), // 等待一个参数add(5) // 等待一个参数
);console.log(addThenMultiply(10)); // 输出: 45
// 计算过程: 10 + 5 = 15, 15 * 3 = 45
2. 异步函数组合
// 异步 compose 函数
const asyncCompose = (...fns) => (value) => fns.reduceRight((acc, fn) => acc.then(fn), Promise.resolve(value));// 异步函数示例
const fetchUser = async (id) => {// 模拟 API 调用return { id, name: `User ${id}`, age: 20 + id };
};const validateUser = async (user) => {if (user.age < 18) throw new Error('User too young');return user;
};const formatUser = async (user) => ({...user,displayName: `${user.name} (${user.age})`
});// 组合异步函数
const getUserPipeline = asyncCompose(formatUser, validateUser, fetchUser);getUserPipeline(1).then(user => console.log(user)).catch(error => console.error(error));
函数组合的优势
- 可读性:代码更加声明式,易于理解
- 可维护性:每个函数职责单一,易于测试和修改
- 可复用性:小函数可以在不同组合中重复使用
- 模块化:复杂的逻辑被分解为简单的部分
注意事项
- 函数纯度:组合的函数应该是纯函数,不产生副作用
- 参数数量:组合的函数通常应该只接受一个参数
- 错误处理:需要考虑组合链中的错误处理机制
