文章目录
柯里化(Currying)是函数式编程中的一个重要概念,它可以将一个多参数的函数转换成一系列单参数函数。本文将详细介绍 JavaScript 中柯里化的实现方法,包括基础实现、高级应用和实际场景。
什么是函数柯里化?
柯里化是指将一个接受多个参数的函数转换为一系列接受单一参数的函数的过程。例如:
// 原始函数
function add(a, b, c) {
return a + b + c;
}
// 柯里化后的函数
function curriedAdd(a) {
return function(b) {
return function(c) {
return a + b + c;
};
};
}
// 调用方式
add(1, 2, 3); // 6
curriedAdd(1)(2)(3); // 6
基础柯里化实现
1. 手动柯里化
// 手动实现三参数函数的柯里化
function multiply(a) {
return function(b) {
return function(c) {
return a * b * c;
};
};
}
// 使用
const result = multiply(2)(3)(4); // 24
console.log(result);
2. 通用柯里化函数
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn.apply(this, args);
} else {
return function(...args2) {
return curried.apply(this, args.concat(args2));
};
}
};
}
// 使用示例
function sum(a, b, c) {
return a + b + c;
}
const curriedSum = curry(sum);
console.log(curriedSum(1)(2)(3)); // 6
console.log(curriedSum(1, 2)(3)); // 6
console.log(curriedSum(1)(2, 3)); // 6
高级柯里化实现
3. 支持占位符的柯里化
function curryWithPlaceholder(fn) {
return function curried(...args) {
// 检查参数是否足够且不包含占位符
const complete = args.length >= fn.length &&
!args.slice(0, fn.length).includes(curryWithPlaceholder.placeholder);
if (complete) {
return fn.apply(this, args);
} else {
return function(...args2) {
// 替换占位符
const combinedArgs = args.map(arg =>
arg === curryWithPlaceholder.placeholder && args2.length ? args2.shift() : arg
).concat(args2);
return curried.apply(this, combinedArgs);
};
}
};
}
// 定义占位符
curryWithPlaceholder.placeholder = Symbol();
// 使用示例
function concat(a, b, c) {
return a + b + c;
}
const curriedConcat = curryWithPlaceholder(concat);
const _ = curryWithPlaceholder.placeholder;
console.log(curriedConcat(1)(2)(3)); // "123"
console.log(curriedConcat(_, 2)(1)(3)); // "123"
console.log(curriedConcat(_, _, 3)(1)(2)); // "123"
console.log(curriedConcat(_, _, 3)(1, 2)); // "123"
console.log(curriedConcat(_, 2, _)(1, 3)); // "123"
4. 无限参数柯里化
function infiniteCurry(fn) {
return function curried(...args) {
return function(...args2) {
const combined = [...args, ...args2];
if (args2.length === 0) {
return combined.reduce((acc, val) => fn(acc, val));
}
return curried(...combined);
};
};
}
// 使用示例
const add = infiniteCurry((a, b) => a + b);
console.log(add(1)(2)(3)(4)()); // 10
console.log(add(1, 2)(3, 4)()); // 10
ES6箭头函数简化版
const curry = fn => {
const curried = (...args) =>
args.length >= fn.length
? fn(...args)
: (...args2) => curried(...args, ...args2);
return curried;
};
// 使用示例
const join = (a, b, c) => [a, b, c].join('-');
const curriedJoin = curry(join);
console.log(curriedJoin('a')('b')('c')); // "a-b-c"
console.log(curriedJoin('a', 'b')('c')); // "a-b-c"
实际应用场景
1. 参数复用
// 创建URL构建器
const createURL = (baseURL, path) => `${baseURL}${path}`;
const curriedCreateURL = curry(createURL);
const withBase = curriedCreateURL('https://api.example.com');
console.log(withBase('/users')); // "https://api.example.com/users"
console.log(withBase('/products')); // "https://api.example.com/products"
2. 事件处理
// 事件监听器
const addEventListener = curry((eventType, element, handler) => {
element.addEventListener(eventType, handler);
});
// 创建特定类型的事件监听器
const onClick = addEventListener('click');
const onHover = addEventListener('mouseover');
// 为特定元素添加事件
const button = document.querySelector('#myButton');
onClick(button, () => console.log('Button clicked!'));
const div = document.querySelector('#myDiv');
onHover(div, () => console.log('Div hovered!'));
3. 函数组合
const compose = (...fns) => x => fns.reduceRight((acc, fn) => fn(acc), x);
const toUpper = str => str.toUpperCase();
const exclaim = str => `${str}!`;
const greet = name => `Hello, ${name}`;
const curriedGreet = curry(greet);
const loudGreeting = compose(exclaim, toUpper, curriedGreet('John'));
console.log(loudGreeting()); // "HELLO, JOHN!"
性能优化版本
function performanceCurry(fn) {
const arity = fn.length;
const memo = new Map();
function generateKey(args) {
return args.join('|');
}
return function curried(...args) {
const key = generateKey(args);
if (memo.has(key)) {
return memo.get(key);
}
if (args.length >= arity) {
const result = fn.apply(this, args);
memo.set(key, result);
return result;
} else {
const next = function(...args2) {
return curried.apply(this, args.concat(args2));
};
memo.set(key, next);
return next;
}
};
}
// 使用示例
const heavyCompute = (a, b, c) => {
// 模拟耗时计算
for (let i = 0; i < 100000000; i++) {}
return a + b + c;
};
const curriedCompute = performanceCurry(heavyCompute);
console.time('第一次调用');
console.log(curriedCompute(1)(2)(3)); // 6
console.timeEnd('第一次调用');
console.time('第二次调用相同参数');
console.log(curriedCompute(1)(2)(3)); // 6 (从缓存读取)
console.timeEnd('第二次调用相同参数');
柯里化 vs 部分应用
柯里化和部分应用(Partial Application)经常被混淆,但它们有所不同:
// 柯里化 - 每次只接受一个参数
function curriedAdd(a) {
return function(b) {
return function(c) {
return a + b + c;
};
};
}
// 部分应用 - 可以一次接受多个参数
function partialAdd(a, b) {
return function(c) {
return a + b + c;
};
}
// 使用
curriedAdd(1)(2)(3); // 柯里化
partialAdd(1, 2)(3); // 部分应用
总结
JavaScript 中实现函数柯里化的关键点:
- 基础实现:通过检查参数长度决定返回结果还是继续柯里化
- 高级功能:支持占位符、无限参数、性能优化等
- 实际应用:参数复用、事件处理、函数组合等场景
- 注意事项:
- 柯里化会增加函数调用层次,可能影响性能
- 不是所有函数都适合柯里化,特别是参数相互依赖的情况
- 在需要延迟执行或参数复用的场景下特别有用
最佳实践建议:
- 对于固定参数数量的函数,使用通用柯里化函数
- 需要灵活参数顺序时,使用占位符实现
- 性能敏感场景考虑使用缓存优化版本
- 明确区分柯里化和部分应用的使用场景
柯里化是函数式编程中的重要技术,合理使用可以使代码更加模块化、可复用和声明式。
文章目录
柯里化(Currying)是函数式编程中的一个重要概念,它可以将一个多参数的函数转换成一系列单参数函数。本文将详细介绍 JavaScript 中柯里化的实现方法,包括基础实现、高级应用和实际场景。
什么是函数柯里化?
柯里化是指将一个接受多个参数的函数转换为一系列接受单一参数的函数的过程。例如:
// 原始函数
function add(a, b, c) {
return a + b + c;
}
// 柯里化后的函数
function curriedAdd(a) {
return function(b) {
return function(c) {
return a + b + c;
};
};
}
// 调用方式
add(1, 2, 3); // 6
curriedAdd(1)(2)(3); // 6
基础柯里化实现
1. 手动柯里化
// 手动实现三参数函数的柯里化
function multiply(a) {
return function(b) {
return function(c) {
return a * b * c;
};
};
}
// 使用
const result = multiply(2)(3)(4); // 24
console.log(result);
2. 通用柯里化函数
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn.apply(this, args);
} else {
return function(...args2) {
return curried.apply(this, args.concat(args2));
};
}
};
}
// 使用示例
function sum(a, b, c) {
return a + b + c;
}
const curriedSum = curry(sum);
console.log(curriedSum(1)(2)(3)); // 6
console.log(curriedSum(1, 2)(3)); // 6
console.log(curriedSum(1)(2, 3)); // 6
高级柯里化实现
3. 支持占位符的柯里化
function curryWithPlaceholder(fn) {
return function curried(...args) {
// 检查参数是否足够且不包含占位符
const complete = args.length >= fn.length &&
!args.slice(0, fn.length).includes(curryWithPlaceholder.placeholder);
if (complete) {
return fn.apply(this, args);
} else {
return function(...args2) {
// 替换占位符
const combinedArgs = args.map(arg =>
arg === curryWithPlaceholder.placeholder && args2.length ? args2.shift() : arg
).concat(args2);
return curried.apply(this, combinedArgs);
};
}
};
}
// 定义占位符
curryWithPlaceholder.placeholder = Symbol();
// 使用示例
function concat(a, b, c) {
return a + b + c;
}
const curriedConcat = curryWithPlaceholder(concat);
const _ = curryWithPlaceholder.placeholder;
console.log(curriedConcat(1)(2)(3)); // "123"
console.log(curriedConcat(_, 2)(1)(3)); // "123"
console.log(curriedConcat(_, _, 3)(1)(2)); // "123"
console.log(curriedConcat(_, _, 3)(1, 2)); // "123"
console.log(curriedConcat(_, 2, _)(1, 3)); // "123"
4. 无限参数柯里化
function infiniteCurry(fn) {
return function curried(...args) {
return function(...args2) {
const combined = [...args, ...args2];
if (args2.length === 0) {
return combined.reduce((acc, val) => fn(acc, val));
}
return curried(...combined);
};
};
}
// 使用示例
const add = infiniteCurry((a, b) => a + b);
console.log(add(1)(2)(3)(4)()); // 10
console.log(add(1, 2)(3, 4)()); // 10
ES6箭头函数简化版
const curry = fn => {
const curried = (...args) =>
args.length >= fn.length
? fn(...args)
: (...args2) => curried(...args, ...args2);
return curried;
};
// 使用示例
const join = (a, b, c) => [a, b, c].join('-');
const curriedJoin = curry(join);
console.log(curriedJoin('a')('b')('c')); // "a-b-c"
console.log(curriedJoin('a', 'b')('c')); // "a-b-c"
实际应用场景
1. 参数复用
// 创建URL构建器
const createURL = (baseURL, path) => `${baseURL}${path}`;
const curriedCreateURL = curry(createURL);
const withBase = curriedCreateURL('https://api.example.com');
console.log(withBase('/users')); // "https://api.example.com/users"
console.log(withBase('/products')); // "https://api.example.com/products"
2. 事件处理
// 事件监听器
const addEventListener = curry((eventType, element, handler) => {
element.addEventListener(eventType, handler);
});
// 创建特定类型的事件监听器
const onClick = addEventListener('click');
const onHover = addEventListener('mouseover');
// 为特定元素添加事件
const button = document.querySelector('#myButton');
onClick(button, () => console.log('Button clicked!'));
const div = document.querySelector('#myDiv');
onHover(div, () => console.log('Div hovered!'));
3. 函数组合
const compose = (...fns) => x => fns.reduceRight((acc, fn) => fn(acc), x);
const toUpper = str => str.toUpperCase();
const exclaim = str => `${str}!`;
const greet = name => `Hello, ${name}`;
const curriedGreet = curry(greet);
const loudGreeting = compose(exclaim, toUpper, curriedGreet('John'));
console.log(loudGreeting()); // "HELLO, JOHN!"
性能优化版本
function performanceCurry(fn) {
const arity = fn.length;
const memo = new Map();
function generateKey(args) {
return args.join('|');
}
return function curried(...args) {
const key = generateKey(args);
if (memo.has(key)) {
return memo.get(key);
}
if (args.length >= arity) {
const result = fn.apply(this, args);
memo.set(key, result);
return result;
} else {
const next = function(...args2) {
return curried.apply(this, args.concat(args2));
};
memo.set(key, next);
return next;
}
};
}
// 使用示例
const heavyCompute = (a, b, c) => {
// 模拟耗时计算
for (let i = 0; i < 100000000; i++) {}
return a + b + c;
};
const curriedCompute = performanceCurry(heavyCompute);
console.time('第一次调用');
console.log(curriedCompute(1)(2)(3)); // 6
console.timeEnd('第一次调用');
console.time('第二次调用相同参数');
console.log(curriedCompute(1)(2)(3)); // 6 (从缓存读取)
console.timeEnd('第二次调用相同参数');
柯里化 vs 部分应用
柯里化和部分应用(Partial Application)经常被混淆,但它们有所不同:
// 柯里化 - 每次只接受一个参数
function curriedAdd(a) {
return function(b) {
return function(c) {
return a + b + c;
};
};
}
// 部分应用 - 可以一次接受多个参数
function partialAdd(a, b) {
return function(c) {
return a + b + c;
};
}
// 使用
curriedAdd(1)(2)(3); // 柯里化
partialAdd(1, 2)(3); // 部分应用
总结
JavaScript 中实现函数柯里化的关键点:
- 基础实现:通过检查参数长度决定返回结果还是继续柯里化
- 高级功能:支持占位符、无限参数、性能优化等
- 实际应用:参数复用、事件处理、函数组合等场景
- 注意事项:
- 柯里化会增加函数调用层次,可能影响性能
- 不是所有函数都适合柯里化,特别是参数相互依赖的情况
- 在需要延迟执行或参数复用的场景下特别有用
最佳实践建议:
- 对于固定参数数量的函数,使用通用柯里化函数
- 需要灵活参数顺序时,使用占位符实现
- 性能敏感场景考虑使用缓存优化版本
- 明确区分柯里化和部分应用的使用场景
柯里化是函数式编程中的重要技术,合理使用可以使代码更加模块化、可复用和声明式。