进来了吗? 应该是进来了。 by the way,这里没有六脉神剑,甚至和六脉神剑八杆子打不着边,可这篇博文的标题就叫这个,不改了,况且改成什么?

回正题, javascript 中的函数柯里化,网上很多文章都有写,五花八门,甚至不乏奇技淫巧。(实际工作大家中用的很少吧)笔者看了一些,原本我认为对它还算了解,看着看着越迷糊了,于是整理花了点时间简单整理了一下,希望对大家有点帮助。

权威出处

函数柯里化(function currying),它用于创建已经设置好了一个或多个参数的函数。函数柯里化的基本方法和函数绑定是一样的:使用一个闭包返回一个函数。两者的区别在于,当函数被调用时,返回的函数还需要设置一些传入的参数。

function add(num1, num2){ 
 return num1 + num2; 
} 
function curriedAdd(num2){ 
 return add(5, num2); 
} 
alert(add(2, 3)); //5 
alert(curriedAdd(3)); //8

这段代码定义了两个函数:add()和 curriedAdd()。后者本质上是在任何情况下第一个参数为 5 的 add()版本。尽管从技术上来说curriedAdd()并非柯里化的函数,但它很好地展示了其概念。

柯里化函数通常由以下步骤动态创建:调用另一个函数并为它传入要柯里化的函数和必要参数。下面是创建柯里化函数的通用方式。

function curry(fn){
  var args=Array.prototype.slice.call(arguments, 1);
  return function(){
    var innerArgs=Array.prototype.slice.call(arguments);
    var finalArgs=args.concat(innerArgs);
    return fn.apply(null, finalArgs);
  };
}

以上内容来自于 《javascript高级程序设计》 红皮书。

注意加粗部分 curriedAdd() 并非柯里化函数

拓展A

来源一
某面试题,请实现一柯里化函数
然后原博贴了这么一段代码

const curry = (fn, ...args1) => (...args2) => (
 arg => arg.length === fn.length ? fn(...arg) : curry(fn, ...arg)
)([...args1, ...args2]);

// 调用
const foo = (a, b, c) => a * b * c;
curry(foo)(2, 3, 4); // -> 24
curry(foo, 2)(3, 4); // -> 24
curry(foo, 2, 3)(4); // -> 24
curry(foo, 2, 3, 4)(); // -> 24

看到上面的代码,必须来一句 "卧槽" 箭头函数原本可用来简化函数书写,这上面连续3箭头看得我有点晕乎,最后面括号内的参数到底传给谁了? 把它搞的好看一点

const curry = function(fn, ...args1){
    
    return function (...args2) {
        
        return function(arg){
            return arg.length === fn.length ? fn(...arg) : curry(fn, ...arg)
        }([...args1, ...args2]);
     
    }
}

常规的通用柯里化函数只是函数里返回函数,这个是函数里返回函数,里面的函数再返回一函数,这最内部函数直接在中间函数被调用了。
这个通用curry 函数的原理类似递归调用,通过判断参数个数。 假如原始函数 fn 有8个参数,只要传递的参数不够8个,它就继续递归调用自身,够,就调用原始函数 fn

拓展B

来源二
实现一个add方法,使计算结果能够满足如下预期:
add(1)(2)(3) = 6;
add(1, 2, 3)(4) = 10;
add(1)(2)(3)(4)(5) = 15;

function add() {
    // 第一次执行时,定义一个数组专门用来存储所有的参数
    var _args = Array.prototype.slice.call(arguments);

    // 在内部声明一个函数,利用闭包的特性保存_args并收集所有的参数值
    var _adder = function() {
    _args.push(...arguments);
    return _adder;
    };

    // 利用toString隐式转换的特性,当最后执行时隐式转换,并计算最终的值返回
    _adder.toString = function () {
        return _args.reduce(function (a, b) {
            return a + b;
        });
    }
    return _adder;
}

以上内容代码完全引用自"来源二",下面是我个人的实验:

有点意思,但我们用如下方式调用它时,确实能达到预期效果

console.log("add(1)(2)(3):",add(1)(2)(3));
console.assert(add(2, 6)(1)!=9, "add(2, 6)(1)!=9");
console.log(add(1, 2, 3)(4)=="10")
console.log(add(1, 2, 3)(4)==10)
console.log(add(1, 2, 3)(4)==="10")

输出结果为:
add(1)(2)(3): 6
Assertion failed: add(2, 6)(1)!=9
true
true
false

貌似又不太严格。 add 明显是一个函数,如果调用它,它返回的其实也是一个内部函数,只是这个内部函数重写了 toString 从而使得在一些默认的转换处得到了预期数值。 与之对应的有 valueOf

再在原 add 函数中增加如下代码

_adder.valueOf = function () {
  return 0;
}

那么,你认为如下代码的输出是什么?

console.log(add(1, 2, 3)(4)) 
console.log("hello boy:",add(1, 2, 3)(4)) 
console.log(add(1, 2, 3)(4)=="10") 
console.log(add(1, 2, 3)(4)==10)

...思考中...

四行结果分别是:
{ [Function: _adder] toString: [Function], valueOf: [Function] }
hello boy: 10
false
false

----有没有惊喜?

所以

有没有收获


neveryield
49 声望4 粉丝

资深互联网 noiser