在一些函数式编程语言里面,对函数的描述不是被调用,而是被应用。而在JS里面,我们可以用Function.prototype.apply()来“应用”一个函数。
// define a function var sayHi = function (who) { return "Hello" + (who ? ", " + who : "") + "!"; }; // invoke a function sayHi(); // "Hello" sayHi('world'); // "Hello, world!" // apply a function sayHi.apply(null, ["hello"]); // "Hello, hello!"
以上我们可以认为,调用一个函数实际上就是在给它应用一堆参数。那是否可以只传一部分参数而不传全部呢?
假设有一个函数add(),用于将x与y相加,下面是计算步骤:
// for illustration purposes // not valid JavaScript // we have this function function add(x, y) { return x + y; } // and we know the arguments add(5, 4); // step 1 -- substitute one argument function add(5, y) { return 5 + y; } // step 2 -- substitute the other argument function add(5, 4) { return 5 + 4; }
step1就是部分应用:我们只给它应用了一个参数。
执行一个部分应用的时候,并不能获得结果,而是另外一个函数。我们假设有一个函数curry(),它处理add()的部分应用:
var add = function (x, y) { return x + y; }; // full application add(5, 4); // 9 //a new function var newadd = curry(add, 5); // applying an argument to the new function newadd(4); // 9
部分应用给了我们另外一个函数,这个函数在稍后调用的时候可以接收其他参数。
让函数理解并处理部分应用的过程,叫做柯里化(Currying)。
我们试着让add()本身来处理部分应用:
// a curried add function add(x, y) { if (typeof y === "undefined") { return function (y) { return x + y; }; } // full application return x + y; }
返回的函数创建了闭包,能访问外部函数的参数。
进一步考虑:能不能对任意一个函数进行处理,得到一个新函数,使它能够处理部分参数?(即上面假设的curry()函数)下面实现curry()函数:
function curry(fn) { var slice = Array.prototype.slice, stored_args = slice.call(arguments, 1); return function () { var new_args = slice.call(arguments), args = stored_args.concat(new_args); return fn.apply(null, args); }; }
❶curry()有点复杂。注意到在JS中arguments不是一个真正的数组,利用Array.prototype.slice()将其转换成数组,以便更好地操作。
❷当curry第一次调用的时候,stored_args存储了调用时除去第一个之外的参数,因为第一个参数是要被柯里化的函数。
❸curry()返回了一个新函数,当这个新函数调用时,它能过闭包访问到已经存储了部分参数的stored_args和slice。
❹最后,新函数只需合并老的部分应用的参数(stored_args)和新的参数(new_args),将其应用到原来的函数fn(通过闭包访问)即可。
下面是一些测试:
// a normal function function add(x, y) { return x + y; } // curry a function to get a new function var newadd = curry(add, 5); newadd(4); // 9 // another option -- call the new function directly curry(add, 6)(7); // 13 ———————————————————————————————— // a normal function function add(a, b, c, d, e) { return a + b + c + d + e; } // works with any number of arguments curry(add, 1, 2, 3)(5, 5); // 16 // two-step currying var addOne = curry(add, 1); addOne(10, 10, 10, 10); // 41 var addSix = curry(addOne, 2, 3); addSix(5, 5); // 16