用柯里化、函数组合和不可变数据来组织你的代码,从而获得可预测性和可测试性。
在 Kodokon 中打开本课JavaScript 把函数当作一等值(first-class values)来对待,这为函数式模式打开了大门。柯里化(currying)把一个 n 元参数的函数变成一连串单参数函数:f(a, b, c) 变成 f(a)(b)(c)。不要把它和部分应用(partial application)混淆,后者在单一步骤中固定住某些参数(fn.bind(null, a))。柯里化在从通用函数创建专用函数方面大放异彩。
const curry = (fn) => {
const arity = fn.length;
return function curried(...args) {
if (args.length >= arity) return fn(...args);
return (...rest) => curried(...args, ...rest);
};
};
const add3 = (a, b, c) => a + b + c;
const addCurried = curry(add3);
console.log(addCurried(1)(2)(3)); // 6
console.log(addCurried(1, 2)(3)); // 6有一个规范上的微妙之处:fn.length 只统计位于第一个默认参数或剩余参数之前的那些参数。(a, b = 1) => {} 的 length 是 1,(...args) => {} 的是 0。因此,一个基于元数的 curry 必须传入参数简单的函数。组合(composition)是下一步:把小的纯函数组装成可读的流水线。
const pipe = (...fns) => (input) =>
fns.reduce((acc, fn) => fn(acc), input);
const trim = (s) => s.trim();
const lower = (s) => s.toLowerCase();
const dashes = (s) => s.replace(/\s+/g, "-");
const toSlug = pipe(trim, lower, dashes);
console.log(toSlug(" Hello World "));
// "hello-world"pipe(f, g, h)(x) 计算的是 h(g(f(x))):你按执行顺序来阅读它。compose 变体则从右到左地应用,就像数学记法一样。这些流水线只有在函数是纯的——没有副作用、没有隐藏的依赖——时才有价值,而这就要求不可变数据:你不去修改一个对象,而是生产出它的一个新版本,此时引用相等就足以检测到一次变化(这是 React 协调机制的核心)。
const state = Object.freeze({
user: { name: "Ada" },
tags: ["js"]
});
const next = {
...state,
tags: [...state.tags, "fp"]
};
const deepCopy = structuredClone(state);
deepCopy.user.name = "Grace"; // independent copybind 有效,柯里化只对箭头函数有效pipe(f, g, h)(x) 等价于什么?const s = Object.freeze({ a: { b: 1 } }) 之后,赋值 s.a.b = 2 能生效吗?s.a 并没有被冻结