JavaScript Currying 函数的演进与实现:从早期技巧到现代模式
本文梳理了JavaScript中Currying(柯里化)技术的发展脉络,从早期基于arguments多态判断的简单实现,到利用正则与eval的复杂尝试,再到现代基于闭包和数组累积的标准模式。文章详细解析了不同阶段代码的逻辑差异,包括如何处理不定参数、链式调用以及最终结果的返回机制,帮助开发者深入理解函数式编程在JS中的实际应用与演变。
早期Currying的多态实现与正则技巧
早期的Currying实现往往带有某种多态特征,其核心逻辑是在函数内部根据传入参数的数量动态选择执行分支。
代码如下:
//http://www.openlaszlo.org/pipermail/laszlo-user/2005-March/000350.html
// ★★On 8 Mar 2005, at 00:06, Steve Albin wrote:
function add(a, b) {
if (arguments.length < 1) {
return add;
} else if (arguments.length < 2) {
return function(c) { return a + c }
} else {
return a + b;
}
}
var myadd = add( 2 );
var total = myadd(3); 随后,日本的一位开发者可能在尚未完全掌握将arguments对象通过Array原生方法转换为数组的技术时,尝试使用复杂的正则表达式配合eval函数,构建出一个更接近现代Currying概念的函数实现。
代码如下:
function curry(fun) {
if (typeof fun != 'function') {
throw new Error("The argument must be a function.");
}
if (fun.arity == 0) {
throw new Error("The function must have more than one argument.");
}
var funText = fun.toString();
var args = /function .*\((.*)\)(.*)/.exec(funText)[1].split(', ');
var firstArg = args.shift();
var restArgs = args.join(', ');
var body = funText.replace(/function .*\(.*\) /, "");
var curriedText =
"function (" + firstArg + ") {" +
"return function (" + restArgs + ")" + body +
"}";
eval("var curried =" + curriedText);
return curried;
} function curry(fun) { if (typeof fun != 'function') { throw new Error("The argument must be a function."); } if (fun.arity == 0) { throw new Error("The function must have more than one argument."); } var funText = fun.toString(); var args = /function .*\((.*)\)(.*)/.exec(funText)[1].split(', '); var firstArg = args.shift(); var restArgs = args.join(', '); var body = funText.replace(/function .*\(.*\) /, ""); var curriedText = "function (" + firstArg + ") {" + "return function (" + restArgs + ")" + body + "}"; eval("var curried =" + curriedText); return curried; } function sum(x, y) { return x + y; } function mean3(a, b, c) { return (a + b + c)/3; } var a = curry(sum)(10)(15) alert(a)//25 var b = curry(mean3)(10)(20, 30); alert(b)//20 var c = curry(curry(sum))(10)()(20); alert(c); var d = curry(curry(mean3)(10))(20)(30); alert(d); [Ctrl+A 全选 注:引入外部Js需再刷新一下页面才能执行]
现代Currying的闭包与数组处理
随着闭包概念的普及以及将arguments转换为数组技术的成熟,现代Currying函数得以正式确立。这如同15至17世纪的大航海时代地理大发现,极大地拓展了JavaScript的应用视野。
代码如下:
//一个简单的现代currying函数
function curry (fn, scope) {
var scope = scope || window;
var args = [];
for (var i=2, len = arguments.length; i < len; ++i) {
args.push(arguments[i]);
};
return function() {
fn.apply(scope, args);
};
} 不定参数累积与链式调用优化
常规的Currying函数通常只支持两层调用:第一次调用若参数不足则返回内部函数,第二次调用才执行最终计算。针对这一特性,我们可以对参数处理进行进一步优化,参考以下函数实现。
代码如下:
function sum(){
var result=0;
for(var i=0, n=arguments.length; i<n; i++){
result += arguments[i];
}
return result;
}
alert(sum(1,2,3,4,5)); // 15 这种实现消除了参数数量不足的问题,即使只传入一个参数也能进行计算。若完全不传入参数,则直接返回结果。关键在于区分是否有参数传入:我们可以让函数不断自我调用,只要存在有效参数就继续累积;当检测到无参数传入时,一次性执行最终计算。换言之,前面的步骤仅用于存储参数。
var sum2= curry(sum);
sum2= sum2(1)(2)(3)(4)(5);
sum2(); // 15
相较于普通的Currying函数,上述实现具有一定的复杂性,具体逻辑请参见代码注释。
代码如下:
var curry= function(fn){//原函数的参数为函数
return function(args){//内部函数的参数为数组,由于立即执行,因此直接到第三重去
//args是相对于第三重内部函数可是全局变量
var self= arguments.callee;//把自身保存起来(就是那个数组为参数的第二重函数)
return function(){ //这才是第二次调用的函数
if(arguments.length){//如果还有要添加的参数
[].push.apply(args,arguments);//apply把当前传入的所有参数放进args中
return self(args);
}else{
return fn.apply(this,args);//apply的第二参数为数组
}
}
}([]);
}; function sum(){ var result=0; for(var i=0, n=arguments.length; i<n; i++){ result += arguments[i]; } return result; }; var curry = function(fn){//原函数的参数为函数 return function(args){//内部函数的参数为数组,由于立即执行,因此直接到第三重去 var self= arguments.callee;//把自身保存起来 return function(){ //这才是第二次调用的函数 if(arguments.length){//如果还有要添加的参数 [].push.apply(args,arguments); return self(args); } else return fn.apply(this,args);//执行 } }([]); }; var sum2= curry(sum); sum2= sum2(1)(2)(3)(4)(5); alert(sum2()); [Ctrl+A 全选 注:引入外部Js需再刷新一下页面才能执行]
或者,每次调用时传入多个参数:
function sum(){ var result=0; for(var i=0, n=arguments.length; i<n; i++){ result += arguments[i]; } return result; }; var curry = function(fn){//原函数的参数为函数 return function(args){//内部函数的参数为数组,由于立即执行,因此直接到第三重去 var self= arguments.callee;//把自身保存起来 return function(){ //这才是第二次调用的函数 if(arguments.length){//如果还有要添加的参数 [].push.apply(args,arguments); return self(args); } else return fn.apply(this,args);//执行 } }([]); }; var sum2= curry(sum); sum2= sum2(1,2,3); sum2= sum2(4,5,6); sum2= sum2(7,8,9); alert(sum2()); [Ctrl+A 全选 注:引入外部Js需再刷新一下页面才能执行]
忽略多余参数的高级实现方案
然而,上述函数存在一个不足之处:最终必须添加一个空括号才能触发执行。如果我们希望仅在参数足够时直接返回结果,而忽略多余参数,可以进行如下改进。
代码如下:
function curry(f) {
if (f.length == 0) return f;
function iterate(args) {
if (args.length <= f.length)
return f.apply(null, args);
return function () {
return iterate(args.concat(Array.prototype.slice.call(arguments)));
};
}
return iterate([]);
} function curry(f) { if (f.length == 0) return f; function iterate(args) { if (args.length >= f.length) return f.apply(null, args); return function () { return iterate(args.concat(Array.prototype.slice.call(arguments))); }; } return iterate([]); } function mean3(a, b, c) { return (a + b + c) / 3; } var curriedMean3 = curry(mean3); alert(curriedMean3(1)(2, 3)); // => 2 alert(curriedMean3(1)(2)(3));//空括号无效 alert(curriedMean3()(1)()(2)()(3)); // => 2 alert(curriedMean3(1, 2)(3, 4)); // => 2 (第四个参数无效) 复制代码
D3.js 比例尺与坐标轴的配置详解
2026-11-05 16:54:46
JavaScript怎么遍历对象的Symbol键和不可枚举属性
2026-11-05 13:13:39
封装一个健壮的JS字符串判空工具函数
2026-11-05 12:16:14
JavaScript合并数组的三种常用方法:concat、扩展运算符与push
2026-11-05 11:22:29
JavaScript立即执行函数(IIFE)是什么及基本语法
2026-11-05 10:51:19
