偏应用(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岁,住在北京"

注意事项

  1. 不要过度使用:只有频繁重复相同参数时才值得用
  2. 可读性优先:如果偏应用让代码更难懂,直接用原函数
  3. bind 的坑bind 的第一个参数是 this,用 null 表示不关心

一句话总结

偏应用 = 预制半成品
先把常用调料配好(固定参数),以后每次只需要加主料(剩余参数)就能快速出菜。