今天下午研究了一下函数柯里化,把我能看的困得呀,看完以后,总有种很亏的感觉,感觉浪费了两三个小时,去理解了一个不怎么用的东西,但是作为一个算法的了解,还是写篇博客吧,毕竟看了一下午。
比较官方的说法是,把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。
在直觉上,柯里化声称如果你固定某些参数,你将得到接受余下参数的一个函数。
我们先看一个非常简单的函数。
现在,我们要实现一个求三个数之和的函数:
function add(x, y, z) {
return x + y + z;
}
console.log(add(1, 2, 3)); //6
这样一个简单的函数,我们可以通过一个柯里化的思想改变一下,只传入一个参数,然后返回的函数处理剩余的参数,可以改写成如下
let add = function(x) {
return function(y) {
return function(z) {
return x + y + z;
};
};
}
let addFirst = add(1);
let addSecond = addFirst(2);
let addThird = addSecond(3);
console.log(addThird); // 6
利用闭包,可以轻易达成这样的效果。
那么接下来,我们将他优化一下。
//柯里化函数
let curryDelay = function(fn) {
let _content = [];
return function accu() {
if (arguments.length === 0) {
return fn(..._content);
} else {
_content.push(...Array.prototype.slice.call(arguments));
return accu;
}
};
};
//求和函数
let add = function() {
let sum = 0;
for(let i of arguments) {
sum += i;
}
return sum;
}
//柯里化
let addCurry = currying(add);
addCurry(1); //无返回值
addCurry(2); //无返回值
addCurry(3); //无返回值
addCurry(4); //无返回值
addCurry(); //10
//也可以连用
addCurry(1)(2)(3)(4)(); //10
可以看出一个求和函数被我们柯里化了,在执行完以后,他返回了一个函数,这个函数负责处理剩余的参数(后传进去的),而在不传参数之后,进行’结算’,将之前的所有参数的和输出。
这是我最头痛的,可能还是太菜,柯里化还是没有用到,希望之后能用到。
柯里化的作用大致有三个:
* 延迟执行
* 参数复用
* 动态生成函数
而我们上面的代码,就是柯里化一种延迟执行的用法,只要你执行中有参数,而且不论多少参数,他都会先存起来,等待你执行函数不带参数时,开始运算输出。就像是延迟了一样,这个在平时敲代码过程中,可能用到的是最少的。
参数复用,当多次调用一个函数,并且传递的参数绝大多数是相同的时候,那么该函数就是一个很好的柯里化候选。
比如现在有这样一个函数
function print (name, subject, score) {
let result = `${name}的${subject}得了${score}分`;
console.log(result);
}
print('小明', '数学', '150'); //小明的数学得了150分
print('小明', '语文', '150'); //小明的语文得了150分
print('小明', '英语', '150'); //小明的英语得了150分
这里面参数name一直为相同的值,并且函数中也是一样的操作。
我们可以修改成如下
let currying = function(fn) {
let _args = Array.prototype.slice.call(arguments, 1);
return function() {
let _newArgs = [..._args, ...Array.prototype.slice.call(arguments)];
return fn(..._newArgs);
};
};
//柯里化
let printCurry = curryAgain(print, '小明');
printCurry('数学', '150'); //小明的数学得了150分
printCurry('语文', '150'); //小明的语文得了150分
printCurry('英语', '150'); //小明的英语得了150分
同学们可能发现,代码变复杂了许多,而并没有多大用处,这我就要和大家说说了
因为我们写的例子很简单,函数操作不怎么复杂,而且需要复用的参数也只有一个,那么在平时的项目中,我们函数是很复杂的,参数复用是很。。多。。好像也不多,emmmmm好吧,你说的没错,就是没毛用
但是有一个最经典的却是用到了这个作用~就是bind的实现,后面会给出代码
动态创建函数算是比较有用的吧,那么他可以减少一些判断操作(if, else),最经典的可能要属兼容浏览器的添加事件方法,在平常为了兼容IE(其实根本没兼容过这个智障浏览器),我们会这么写
const addEvent = function (el, type, fn, cature) {
if (window.addEventListener) {
el.addEventListener(type, (e) => fn.call(el, e), capture);
} else if (window.attachEvent) {
el.attachEvent('on' + type, (e) => fn.call(el, e);
}
}
上面的代码有什么问题呢?很显然,我们每次使用它的时候,都会跑一边if…else…其实,用函数柯里化,我们只需要跑一遍,代码如下
const addEvent = (function(){
if (window.addEventListener) {
return function(el, type, fn, capture) {
el.addEventListener(type, (e) => fn.call(el, e), capture);
};
} else if (window.attachEvent) {
return function(el, type, fn, capture) {
el.attachEvent('on' + type, (e) => fn.call(el, e);
};
}
})();
这样写,我们只需要每次在代码的前方声明一下,就可以使用这个函数了,剩余的参数应用都是返回的函数来实现的
柯里化在平时的应用是少的可怜,有一个最经典的实现bind,我大概说一下
Function.prototype.bind = function(context) {
let me = this;
let args = Array.prototype.slice.call(arguments, 1);
return function() {
let innerArgs = Array.prototype.slice.call(arguments);
let finalArgs = args.concat(innerArgs);
return me.apply(context, finalArgs);
}
}
这就是bind的一个简易实现,他借用call和apply很方便的实现了bind。当然原本的方法肯定不是这样的,考虑的东西要更多。
最后,我觉得柯里化更像是一种闭包的应用,其中还有apply和call的各种用法,虽然平时不怎么用到,但确实是一种提高能力的算法,还是值得研究一番的。