偏应用(Partial Application)入门指南
一句话理解
偏应用就是提前固定函数的一部分参数,生成一个新函数,新函数只需要接收剩下的参数。
生活中的类比
你去奶茶店点单:
- 原函数:
makeTea(茶底, 甜度, 加料) - 偏应用:你先固定
茶底 = '乌龙',得到一个orderUlongTea(甜度, 加料)的新菜单 - 下次点单只需说「七分糖、加珍珠」,不用再说「乌龙」
2种基本写法
1. 用 bind 实现(最原生)
function multiply(a, b, c) {
return a * b * c;
}
// 固定第一个参数为 2
const multiplyBy2 = multiply.bind(null, 2);
multiplyBy2(3, 4); // 24 (2 * 3 * 4)
// 固定前两个参数
const doubleTriple = multiply.bind(null, 2, 3);
doubleTriple(5); // 30 (2 * 3 * 5)
2. 手动实现 偏应用函数
function partial(fn, ...fixedArgs) {
return function(...remainingArgs) {
return fn(...fixedArgs, ...remainingArgs);
};
}
// 使用
function greet(greeting, name, punctuation) {
return `${greeting}, ${name}${punctuation}`;
}
const sayHello = partial(greet, '你好');
sayHello('小明', '!'); // "你好, 小明!"
const sayHiExcite = partial(greet, '嗨', '小红');
sayHiExcite('!!'); // "嗨, 小红!!"
常见应用场景
1. API 请求封装
function request(baseURL, method, endpoint, data) {
return fetch(`${baseURL}${endpoint}`, {
method,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
}
// 固定 baseURL
const apiRequest = partial(request, 'https://api.example.com');
// 进一步固定 method
const get = partial(apiRequest, 'GET');
const post = partial(apiRequest, 'POST');
// 使用
get('/users'); // GET https://api.example.com/users
post('/users', { name: '小明' }); // POST https://api.example.com/users
2. 事件监听
function handleEvent(eventType, element, handler) {
element.addEventListener(eventType, handler);
}
// 固定事件类型
const onClick = partial(handleEvent, 'click');
const onHover = partial(handleEvent, 'mouseenter');
onClick(button, () => console.log('点击了'));
onHover(div, () => console.log('悬停了'));
3. 数据处理
function formatNumber(locale, decimals, number) {
return new Intl.NumberFormat(locale, {
minimumFractionDigits: decimals,
maximumFractionDigits: decimals
}).format(number);
}
// 固定为人民币格式
const formatCNY = partial(formatNumber, 'zh-CN', 2);
formatCNY(1234.5); // "1,234.50"
formatCNY(99.9); // "99.90"
// 固定为美元格式
const formatUSD = partial(formatNumber, 'en-US', 2);
formatUSD(1234.5); // "1,234.50"
偏应用 vs 柯里化
| 对比项 | 偏应用 | 柯里化 |
|---|---|---|
| 参数固定方式 | 一次可固定多个参数 | 每次只传一个参数 |
| 返回的函数 | 仍可接收多个参数 | 始终只接收一个参数 |
| 灵活性 | 可固定任意位置的参数 | 严格从左到右 |
| 实现复杂度 | 简单 | 相对复杂 |
// 偏应用:一次固定多个
const f1 = partial(add, 1, 2);
f1(3); // 6
// 柯里化:每次一个
const f2 = curry(add);
f2(1)(2)(3); // 6
进阶:固定任意位置的参数
有时候想固定中间的参数,可以用占位符:
// 带占位符的偏应用
function partialWithPlaceholder(fn, ...args) {
return function(...remainingArgs) {
let argsIndex = 0;
let remainingIndex = 0;
const finalArgs = args.map(arg =>
arg === '_' ? remainingArgs[remainingIndex++] : arg
);
return fn(...finalArgs, ...remainingArgs.slice(remainingIndex));
};
}
// 使用 _ 作为占位符
const _ = '_';
function introduce(name, age, city) {
return `${name}今年${age}岁,住在${city}`;
}
// 固定 age,留 name 和 city 待填
const withAge = partialWithPlaceholder(introduce, _, 25, _);
withAge('小明', '北京'); // "小明今年25岁,住在北京"
注意事项
- 不要过度使用:只有频繁重复相同参数时才值得用
- 可读性优先:如果偏应用让代码更难懂,直接用原函数
- bind 的坑:
bind的第一个参数是 this,用null表示不关心
一句话总结
偏应用 = 预制半成品
先把常用调料配好(固定参数),以后每次只需要加主料(剩余参数)就能快速出菜。
