柯里化的定义
引自维基百科
在计算机科学中,柯里化(英语:Currying),又译为卡瑞化或加里化,是把接受多个参数)的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。
例子
function(a b) {
return a + b;
}
// 正常执行
add(1, 2) // 3
// 假设有个 curry 的函数
const addCurry = curry(add);
// 柯里化后执行方式
addCurry(1,2) // 3
addCurry(1)(2) // 3
用途
好了我们已经知道了什么是柯里化了,但柯里化到底有什么用了?
例子
// 一个简答的 ajax 请求
function ajax(type, url, data) {
const xhr = new XMLHttpRequest();
xhr.open(type, url, true);
xhr.send(data);
}
// 每次发送一个 ajax 请求都需要写上一系列的参数,显然这样的非常大麻烦。
ajax("POST", "www.example.com", "name=cxx");
ajax("GET", "www.example.com", "name=cxx");
// 利用 curry
const ajaxCurry = curry(ajax);
// 以 POST 类型请求数据
const post = ajaxCurry("POST");
post("www.example.com", "name=cxx");
// 以 POST 类型请求来自于 www.example.com 的数据
const postFromTest = post("www.example.com");
postFromTest("name=cxx");
现在好了,我们知道了 curry 的作用。
- 参数复用。
- 延迟计算/运行。
实现 curry 函数(第一版)
const curry = function (fn) {
const args = [].slice.call(arguments, 1);
return function () {
const newArgs = args.concat([].slice.call(arguments));
return fn.apply(this, newArgs);
};
};
const addCurry1 = curry(add, 1, 2);
addCurry1(); // 3
//或者
const addCurry2 = curry(add, 1);
addCurry2(2); // 3
//或者
const addCurry3 = curry(add);
addCurry3(1, 2); // 3
已经有柯里化的感觉了,但还是没有达到要求。不过可以借助这个函数来实现真正的 curry 函数。
实现 curry 函数(第二版)
// 第二版
function sub_curry(fn) {
const args = [].slice.call(arguments, 1);
return function () {
return fn.apply(this, args.concat([].slice.call(arguments)));
};
}
function curry(fn, length) {
length = length || fn.length;
const slice = Array.prototype.slice;
return function () {
if (arguments.length < length) {
const combined = [fn].concat(slice.call(arguments));
return curry(sub_curry.apply(this, combined), length - arguments.length);
} else {
return fn.apply(this, arguments);
}
};
}
const fn = curry(function (a, b, c) {
return [a, b, c];
});
// 以下输出都是 ['a', 'b', 'c']
console.log(fn("a", "b", "c"));
console.log(fn("a", "b")("c"));
console.log(fn("a")("b")("c"));
console.log(fn("a")("b", "c"));
这个代码有点难理解,举一个简单的例子方便理解。
function sub_curry(fn) {
return function () {
return fn();
};
}
function curry(fn, length) {
length = length || 4;
return function () {
if (length > 1) {
return curry(sub_curry(fn), --length);
} else {
return fn();
}
};
}
const fn0 = function () {
console.log(1);
};
const fn1 = curry(fn0);
console.log(fn1()()()()); // 1
先从理解 curry 函数开始。
当执行 fn1() 时,函数返回:
// fn0 看作 A
curry(sub_curry(A))
// 相当于
curry(function(){
return A()
})
当执行 fn1()() 时,函数返回:
curry(sub_curry(function(){
return A()
}))
// 相当于
curry(function(){
return (function(){
return A()
})()
})
// 相当于
curry(function(){
return A()
})
当执行 fn1()()() 时,函数返回:
// 跟 fn1()() 的分析过程一样
curry(function(){
return A()
})
当执行 fn1()()()() 时,因为此时的 length > 2 为 false,所以执行 fn():
fn()
// 相当于
(function(){
return A()
})()
// 相当于
A()
// 执行 A(fn0) 函数,打印 1
更易懂的实现
function curry(fn, args) {
const length = fn.length;
args = args || [];
return function () {
let _args = args.slice(0), arg, i;
for (i = 0; i < arguments.length; i++) {
arg = arguments[i];
_args.push(arg);
}
if (_args.length < length) {
return curry.call(this, fn, _args);
} else {
return fn.apply(this, _args);
}
};
}
const fn = curry(function (a, b, c) {
console.log([a, b, c]);
});
fn("a", "b", "c"); // ["a", "b", "c"]
fn("a", "b")("c"); // ["a", "b", "c"]
fn("a")("b")("c"); // ["a", "b", "c"]
fn("a")("b", "c"); // ["a", "b", "c"]
参考:
[1] JavaScript专题之函数柯里化
[2] 柯里化