前言
柯里化,偏函数,Thunk这三个概念很相似的,今天就来看看有哪些区别。
柯里化(Currying)
把一个多参数的方法,改造成可以接受单一参数的方法,并返回接受剩余参数的新函数。实现:
function curryingHelper(fn, args) {
let length = fn.length;
args = args || []
return function(...rest) {
let _args = args.concat(rest);
return _args.length >= length // 判断收集的参数是否足够
?
fn.apply(this, _args) :
curryingHelper.call(this, fn, _args) // 继续收集参数
}
}
//使用:
function test(a,b,c,d){
console.log('this is ',a,b,c,d);
}
let handle=curryingHelper(test);
handle(1)(2)(3)(4);
handle(1,2)(3,4);
handle(1,2,3)(4);
handle(1)(2,3,4);
代码的主要思路就是收集参数,如果参数的个数满足了,就可以执行了;如果参数的个数没达到,就继续调用返回闭包。
柯里化的主要目的是可以在只知道部分参数的情况下,可以先返回一个方法,等剩余参数获取了,就可执行了。
不过上面的柯里化只能从左往右收集参数,例如function test(a,b,c,d){},我开始只知道c,d参数,后来才知道a,b参数,上面那种就不行了。这种时候就要用一种占位符来先替代未知的参数,等后期参数来了我们再进行合并:
const _ = {};
function crazyCurryingHelper(fn, args) {
let length = fn.length; // 第一遍是fn所需的参数个数,以后是
args = args || [];
return function(...rest) {
let _args = args.slice(),
position = 0,
len = _args.length;
for(let i = 0; i < len; i++) {
_args[i] = _args[i] === _ ? rest[position++] : _args[i]
}
while(position < rest.length) _args.push(rest[position++]);
return _args.filter(n => n != _).length >= length // 递归的进行柯里化
?
fn.apply(this, _args) :
crazyCurryingHelper.call(this, fn, _args)
}
}
//使用
function test(a,b,c,d){
console.log(a,b,c,d);
}
let handle=crazyCurryingHelper(test);
handle(_,_,_,4)(_,_,3)(_,2)(1);
handle(1,_,_,4)(2,3);
handle(1,_,_,4)(_,3)(2);
参数合并的规则有很多,上面的规则是如果遇到了_,就用新参数的;如果不是_就继续遍历。如果新参数遍历后有多余的,就合并在后面,看看下面的一些合并情况:
偏函数(Partial)
偏函数就是固定部分参数,生成另外一个参数更少的方法。例如bind就是一个简单的偏函数:
function test(a,b,c,d){
console.log(a,b,c,d);
}
let handle=test.bind(null,1,2);
handle(3,4);
那么偏函数和柯里化有什么区别呢?柯里化是可以将n个参数的方法拆成n次调用;偏函数则是将n个参数的方法拆成n-x个参数调用和x参数的方法。
栗子:
function text(a,b,c,d){}
柯里化 | 偏函数 |
---|---|
let handle=curry(text) | let handle=curry(text,1) |
handle(1)(2)(3)(4) | handle(2,3,4) |
handle(1,2)(3,4) | let handle=curry(text,1,2) |
handle(1)(2,3,4) | handle(3,4) |
handle(12,3)(4) |
可以看到偏函数和柯里化的主要区别是,柯里化可以多次调用,最多可以调用n次,直到参数拼完整;偏函数只能调用一次,一次就要把所有的参数拼完整。
同样,偏函数也存在位置的问题,上面的是从左往右拼凑参数,如果先知道后面的参数,那么也需要用占位符:
var _ = {};
function partial(fn) {
var args = [].slice.call(arguments, 1);
return function() {
var position = 0, len = args.length;
for(var i = 0; i < len; i++) {
args[i] = args[i] === _ ? arguments[position++] : args[i]
}
while(position < arguments.length) args.push(arguments[position++]);
return fn.apply(this, args);
};
};
//使用
function test(a,b,c,d){
console.log(a,b,c,d);
}
let handle=partial(test,_,_,3,4);
handle(1,2);
Thunk
Thunk方法主要使用在有cb(回调函数)参数的方法中,是指将参数拆分为cb部分和非cb部分:
var Thunk = function(fn){
return function (){
var args = Array.prototype.slice.call(arguments);
return function (callback){
args.push(callback);
return fn.apply(this, args);
}
};
};
//使用
function test(a,b,cb){
cb(a+b);
}
let handle=Thunk(test)(1,2);
handle(function(value){
console.log(value);
});
在划分参数上和偏函数比较类似,偏函数是先固定部分参数,然后调用的时候参数拼凑完整;Thunk是将参数划分为非cb参数和cb参数,也是分为两拨,这是写法上有些许不同:
栗子:
function test(a,b,cb){}
偏函数 | Thunk |
---|---|
let handle= partial(test,1) | let handle=Thunk(test)(1,2) |
handle(2,()=>{}) | handle(()=>{}) |
let handle= partial(test,1,2) | |
handle(()=>{}) |
我们可以看到,偏函数对多参数的划分不是唯一的,但是Thunk的划分是唯一的;Thunk对参数的调用更像柯里化,Thunk是调用了两次才完成参数拼凑,而偏函数只调用了一次。
那么这个Thunk函数有啥作用?其实主要实在异步流程中才会用到,例如Promise,Generator等,常常会将一个Thunk函数封装成一个Promise使用:
var Thunk = function(fn){
return function (){
var args = Array.prototype.slice.call(arguments);
return function (callback){
args.push(callback);
return fn.apply(this, args);
}
};
};
function thunkToPromise(fn) {
return new Promise(function(resolve, reject) {
fn(function(err, res) {
if (err) return reject(err);
resolve(res);
});
});
}
//使用
function getData(url,cb){
setTimeout(function(){
cb(null,{status:1,data:url});
},1000);
}
let handle=Thunk(getData)('http://www.baidu.com');
thunkToPromise(handle).then((value)=>{
console.log(value);
});
这样做的目的是为了让带有cb的方法使用起来和promise差不多,更多关于Thunk的使用方法后续文章会讲解。