函数组合是将多个一元纯函数按右结合顺序串联成新函数的过程,提升可读性、复用性与可维护性;典型实现为compose(f,g,h)等价于f(g(h(x))),需避免副作用、多参及异步函数直接参与。

函数组合(Function Composition)是将多个函数按顺序串联起来,让前一个函数的输出成为后一个函数的输入,最终形成一个新函数的过程。它本身不改变函数行为,但能显著提升代码的可读性、复用性和可维护性——关键在于把“做什么”和“怎么做”清晰分离。
函数组合的核心写法:从右到左执行
JavaScript 中最常用的组合方式是 右结合(right-to-left),即 compose(f, g, h) 等价于 f(g(h(x)))。你可以手动实现一个简单版本:
const compose = (...fns) => x => fns.reduceRight((acc, fn) => fn(acc), x);
例如,要把字符串转大写、去空格、取前5个字符,可以这样写:
立即学习“Java免费学习笔记(深入)”;
const toUpper = s => s.toUpperCase();
const trim = s => s.trim();
const take5 = s => s.substring(0, 5);
const process = compose(take5, trim, toUpper);
process(" hello world "); // → "HELLO"
比起嵌套调用 take5(trim(toUpper(" hello world ")),组合后的代码更贴近自然语言的阅读顺序(先转大写 → 再去空格 → 最后截取),逻辑意图一目了然。
用函数组合替代深层嵌套,降低认知负担
当业务逻辑涉及多步数据转换(如 API 响应处理),嵌套调用容易让人迷失在括号和参数中。函数组合把每一步抽象为命名函数,让调用链变成“名词化流程”:
-
嵌套写法(难追踪):
formatName(validateUser(parseJson(response))) -
组合写法(易理解):
const handleResponse = compose(formatName, validateUser, parseJson); handleResponse(response);
每个函数职责单一、可独立测试,组合后的函数名(如 handleResponse)直接表达用途,而不是实现细节。
配合工具库让组合更自然(如 Ramda 或 Lodash/fp)
手写 compose 足够学习原理,但在工程中推荐使用成熟库。比如 Ramda 的 R.compose 支持自动柯里化和数据最后传入,让组合更流畅:
import { compose, map, filter, propEq } from 'ramda';
const getActiveUsersNames = compose(
map(prop('name')),
filter(propEq('active', true)),
prop('users')
);
getActiveUsersNames({ users: [{ name: 'Alice', active: true }, { name: 'Bob', active: false }] });
// → ['Alice']
这里没有显式传参,函数像积木一样拼接,数据作为“最后一环”流入——这种声明式风格大幅减少临时变量和中间状态,读代码就像读需求文档。
注意:组合要求函数必须是一元的(单参数)且纯的
函数组合依赖可预测的输入输出,因此:
- 避免在组合链中使用带副作用的函数(如
console.log、修改全局状态);如需调试,可用tap类辅助函数 - 若原函数参数多于一个,先用柯里化(
curry)固定部分参数,再参与组合 - 异步函数不能直接组合,需用
pipeP(Ramda)或async/await+ 自定义异步组合器
违反这些原则会导致组合结果不可控,反而损害可读性。










