问题描述
实现一个带占位符合的柯里化函数,例如:
const _ = Symbol.for('placeholder');
function curry() {
}
const fn = curry(function(a, b, c) {
console.log([a, b, c]);
});
fn("a", _, "c")("b"); //>> ["a", "b", "c"]
const fn2 = fn("1", _);
fn2("2")("3") // => ["1", "2", "3"]
fn2("4", "5") // => ["1", "4", "5"]
思路解析
- 先实现一个简单的柯里化的函数,需要注意判断执行原始函数的时机;
- 带占位符号的柯里化函数,需要剔除占位,如何参数中无占位符合且参数长度和原始函数的参数一致时执行原始函数;
代码实现
- 简单版本的curry函数
function curry(fn, context = null) {
return function judge(...args) {
// 参数个数和fn所需要的参数一致,则直接执行fn
if (args.length === fn.length) return fn.apply(context, args);
// 如果参数和fn的不一致,则继续返回一个当前judge函数
return judge.bind(null, ...args);
}
}
- 实现一个带占位符合的curry函数
// 占位符
const _ = Symbol.for('hole');
function curry(fn, context = null) {
// 替换占位符,每次调用时都替换一次
// args 当前调用的参数数组,lastArgsCount上一次调用的参数长度,
// args.length - lastArgsCount = 当前调用新增参数的长度
const replaceHoles = (args, lastArgsCount = 0) => {
if (!lastArgsCount) return args;
// 使用双指针思路替换占位符
let prev = 0;
let last = lastArgsCount;
const newArgs = args.slice(prev, lastArgsCount);
while (prev < lastArgsCount && last < args.length) {
if (newArgs[prev] === _) {
newArgs[prev] = args[last];
last++;
}
prev++;
}
if(last < args.length) {
newArgs.push(...args.slice(last));
}
return newArgs;
}
function judge(...args) {
const params = replaceHoles(args, this.__lastArgsCount);
const hasHole = params.some(arg => arg === _);
if (!hasHole && params.length === fn.length) return fn.apply(context, params);
return judge.bind({__lastArgsCount: params.length}, ...params);
}
// 通过 this 记录上一次调用的参数的长度,重复调用时使用
return judge.bind({__lastArgsCount: 0});
}
测试用例
const fn = curry(function(a, b, c, d, e) {
console.log([a, b, c, d, e]);
});
// 验证 输出全部都是 [1, 2, 3, 4, 5]
fn(1, 2, 3, 4, 5);
fn(_, 2, 3, 4, 5)(1);
fn(1, _, 3, 4, 5)(2);
fn(1, _, 3)(_, 4)(2)(5);
fn(1, _, _, 4)(_, 3)(2)(5);
fn(_, 2)(_, _, 4)(1)(3)(5);
// 重复调用情况
const fn2 = fn(1, _, 3);
fn2(_, 4)(2, 5);
fn2(2, 4, 5);
fn2(2)(_, 5)(4);
fn2(_, _)(2, _)(4, 5);