一、柯里化介绍
柯里化其实是为实现多参函数提供了一个递归降解的实现思路— —把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术
简单理解就是:一个把具有较多参数 的函数转换成具有较少参数函数的过程 。如图:
此图来源于网上
二、简单案例说明
- 问题:求长方体的体积,写一函数。
// 一般
function volume(l,w,h) {
return l * w * h;
}
const aCylinder = volume(100,20,90) // 180000
那么从上面柯里化的概念,我们是不是也可以得出这样的写法:
function volume(l) {
return (w) => {
return (h) => {
return l * w * h
}
}
}
const aCylinder = volume(100)(20)(90) // 180000
通过上面的简单案例,大家对柯里化函数有什么理解?…待
那么,大家有想过一个问题么?如果有一百个参数呢?难道要写一百次?有没有一种方法可以简单的帮我们实现柯里化?
运行案例
function curry(fn) {
var outerArgs = Array.prototype.slice.call(arguments, 1); // 用来接收参数,除了fu之外的参数存进outerArgs
return function() {
var innerArgs = Array.prototype.slice.call(arguments),
finalArgs = outerArgs.concat(innerArgs); // 收集传入的参数,进行缓存
return fn.apply(null, finalArgs);
};
}
const say = (name, prefix, greeting) => `${greeting}, ${prefix} ${name}!`;
const curriedSay = curry(say);
curriedSay('Tom', 'Mr', 'Hello'); // "Hello, Mr Tom!"
curry(say,'Tom', 'Mr')('Hello'); // "Hello, Mr Tom!"
【项目场景中的应用】
讲到这,大家能举例出在在项目中有哪些柯里化的应用么?…待
axios 案例
例:监听事件 EG:
function nodeListen(node, eventName){
return function(fn){
node.addEventListener(eventName, function(){
fn.apply(this, Array.prototype.slice.call(arguments));
}, false);
}
}
var bodyClickListen = nodeListen(document.body, 'click');
bodyClickListen(function(){
console.log('first listen');
});
bodyClickListen(function(){
console.log('second listen');
});
// 使用柯里化,优化监听DOM节点事件。addEventListener三个参数不用每次都写。
三、柯里化使用场景
- 参数复用
固定不变的参数,实现参数复用,比如递归用法。
- 延迟执行
通过bind方法
<div onClick={handleOnClick.bind(null, data)} />
// bind 方法和以上实现的 currying 方法,在功能上有极大的相似,在实现上也几乎差不多。
// 可能唯一的不同就是 bind 方法需要强制绑定 context,也就是 bind 的第一个参数会作为原函数运行时的 this 指向。而 currying 不需要此参数。所以使用 currying 或者 bind 只是一个取舍问题。
Function.prototype.myBind = function(oThis) {
if(typeof this !== 'function') {
return;
}
var self = this,
args = Array.prototype.slice.call(arguments, 1);
return function() {
return self.apply(oThis, args.concat(Array.prototype.slice.call(arguments)));
}
}
function foo() {
console.log(this.a);
}
var obj = { a: 2 };
var bar = foo.myBind(obj);
bar(); // 2
箭头函数
<div onClick={() => handleOnClick(data))} />
// 箭头函数能够实现延迟执行,同时也不像 bind 方法必需指定 context。可能唯一需要顾虑的就是在 react 中,会有人反对在 jsx 标签内写箭头函数,这样子容易导致直接在 jsx 标签内写业务逻辑。
通过currying
<div onClick={currying(handleOnClick, data)} />
- 性能对比
通过jsPerf(js基准测试)测试四种方式的性能,结果为:
箭头函数 > bind > currying > trueCurrying 。
currying 函数相比 bind 函数,其原理相似,但是性能相差巨大,其原因是 bind 由浏览器实现,运行效率有加成。
从这个结果看 Currying 性能无疑是最差的,但是另一方面就算最差的 trueCurrying,而 trueCurrying 方法中实现的自动 Currying 化,是另外三个方法所不具备的。
四、到底需不需要柯里化
- 为什么需要柯里化
1.为了多参函数复用性
2.为函数式编程而生例如:纯函数、compose、container等等事物,也需要用到Currying。
- 为什么不需要柯里化
1.柯里化的一些特性也有其他解决方案,比如提前绑定参数,如箭头函数,bind等。
2. 使用柯里化等函数式特性有额外的性能开销。a. 存取arguments对象通常要比存取命名参数要慢一点.
b. 一些老版本的浏览器在arguments.length的实现上是相当慢的.
c. 创建大量嵌套作用域和闭包函数会带来花销,无论是在内存还是速度上.
五、问题探讨
- 理解以下函数
let curry = function(fn) {
var judge = function(...args) {
if (Boolean(args[args.length - 1])===false) {
return fn(args);//three(null)就到这里了
} else {
return function(...arg) { //one和two和three都是这个函数
return judge(...args, ...arg);
}
}
}
return judge;
}
testCurry = (args)=>{
args.pop();//去掉最后一个参数null
if(args.length<1){
return;
}
let sum = args.reduce((s,n)=>{
return s+n;
},0);
console.log('参数',args);
console.log('sum',sum);
return sum;
};
OnClick =()=>{
console.log('执行了这里 OnClick');
let one = this.curry(this.testCurry)(1);
let two = one(2)(3)(4)(5);
let three = two(6,6,6,6);
three(null);
};
// 函数理解(借鉴去理解文档第二个例子)
judge并没有立即执行,而是在(...arg)=>judge(...args,...arg);方法被调用时才被执行
这个curry后的函数只有在传入的最后一个参数可以被转换为false的时候才开始执行,它存参数的方法也很简单
1,判断如果最后一个参数是否可以是false
2,是的话就把所有保存的参数放到fn里执行,结束
3,否则,返回一个新的匿名函数,这个函数把所有传入参数保存在arg数组中,而这个匿名函数被执行后,就把以前收到的参数数组和当前的参数数组合并后,放到前面说的逻辑中,在judge函数里判断,重复第1步
六、推荐借鉴资料