康巴什网站建设,做暧暧视频免费视频中国网站,字体图标制作网站,官网网站模板概念
用我自己的话来总结一下#xff0c;函数柯里化的意思就是你可以一次传很多参数给curry函数#xff0c;也可以分多次传递#xff0c;curry函数每次都会返回一个函数去处理剩下的参数#xff0c;一直到返回最后的结果。
实例
这里还是举几个例子来说明一下#xff1…概念
用我自己的话来总结一下函数柯里化的意思就是你可以一次传很多参数给curry函数也可以分多次传递curry函数每次都会返回一个函数去处理剩下的参数一直到返回最后的结果。
实例
这里还是举几个例子来说明一下
柯里化求和函数 // 普通方式var add1 function(a, b, c){return a b c;}// 柯里化var add2 function(a) {return function(b) {return function(c) {return a b c;}}}这里每次传入参数都会返回一个新的函数这样一直执行到最后一次返回abc的值。 但是这种实现还是有问题的这里只有三个参数如果哪天产品经理告诉我们需要改成100次我们就重新写100次这很明显不符合开闭原则所以我们需要对函数进行一次修改。
var add function() {var _args [];return function() {if(arguments.length 0) {return _args.reduce(function(a, b) {return a b;})}[].push.apply(_args, arguments);return arguments.callee;}
}
var sum add();
sum(100, 200)(300);
sum(400);
sum(); // 1000我们通过判断下一次是否传进来参数来决定函数是否运行如果继续传进了参数那我们继续把参数都保存起来等运行的时候全部一次性运行这样我们就初步完成了一个柯里化的函数。
通用柯里化函数
这里只是一个求和的函数如果换成求乘积呢我们是不是又需要重新写一遍仔细观察一下我们的add函数如果我们将if里面的代码换成一个函数执行代码是不是就可以变成一个通用函数了
var curry function(fn) {var _args [];return function() {if(arguments.length 0) {return fn.apply(fn, _args);}[].push.apply(_args, arguments);return arguments.callee;}
}
var multi function() {return [].reduce.call(arguments, function(a, b) {return a b;})
}
var add curry(multi);
add(100, 200, 300)(400);
add(1000);
add(); // 2000在之前的方法上面我们进行了扩展这样我们就已经实现了一个比较通用的柯里化函数了。 也许你想问我不想每次都使用那个丑陋的括号结尾怎么办
var curry function(fn) {var len fn.length,args [];return function() {Array.prototype.push.apply(args, arguments)var argsLen args.length;if(argsLen len) {return arguments.callee;}return fn.apply(fn, args);}
}
var add function(a, b, c) {return a b c;
}var adder curry(add)
adder(1)(2)(3)这里根据函数fn的参数数量进行判断直到传入的数量等于fn函数需要的参数数量才会返回fn函数的最终运行结果和上面那种方法原理其实是一样的但是这两种方式都太依赖参数数量了。 我在简书还看到别人的另一种递归实现方法其实实现思路和我的差不多吧。
// 简单实现参数只能从右到左传递
function createCurry(func, args) {var arity func.length;var args args || [];return function() {var _args [].slice.call(arguments);[].push.apply(_args, args);// 如果参数个数小于最初的func.length则递归调用继续收集参数if (_args.length arity) {return createCurry.call(this, func, _args);}// 参数收集完毕则执行funcreturn func.apply(this, _args);}
}这里是对参数个数进行了计算如果需要无限参数怎么办比如下面这种场景。
add(1)(2)(3)(2);
add(1, 2, 3, 4, 5);这里主要有一个知识点那就是函数的隐式转换涉及到toString和valueOf两个方法如果直接对函数进行计算那么会先把函数转换为字符串之后再参与到计算中利用这两个方法我们可以对函数进行修改。
var num function() {
}
num.toString num.valueOf function() {return 10;
}
var anonymousNum (function() { // 10return num;
}())经过修改我们的函数最终版是这样的。参考 前端进阶面试题详细解答
var curry function(fn) {var func function() {var _args [].slice.call(arguments, 0);var func1 function() {[].push.apply(_args, arguments)return func1;}func1.toString func1.valueOf function() {return fn.apply(fn, _args);}return func1;}return func;
}
var add function() {return [].reduce.call(arguments, function(a, b) {return a b;})
}var adder curry(add)
adder(1)(2)(3)那么我们说了那么多柯里化究竟有什么用呢
预加载
在很多场景下我们需要的函数参数很可能有一部分一样这个时候再重复写就比较浪费了我们提前加载好一部分参数再传入剩下的参数这里主要是利用了闭包的特性通过闭包可以保持着原有的作用域。
var match curry(function(what, str) {return str.match(what);
});match(/\s/g, hello world);
// [ ]match(/\s/g)(hello world);
// [ ]var hasSpaces match(/\s/g);
// function(x) { return x.match(/\s/g) }hasSpaces(hello world);
// [ ]hasSpaces(spaceless);
// null上面例子中使用hasSpaces函数来保存正则表达式规则这样可以有效的实现参数的复用。
动态创建函数
这个其实也是一种惰性函数的思想我们可以提前执行判断条件通过闭包将其保存在有效的作用域中来看一种我们平时写代码常见的场景。 var addEvent function(el, type, fn, capture) {if (window.addEventListener) {el.addEventListener(type, function(e) {fn.call(el, e);}, capture);} else if (window.attachEvent) {el.attachEvent(on type, function(e) {fn.call(el, e);});} };在这个例子中我们每次调用addEvent的时候都会重新进行if语句进行判断但是实际上浏览器的条件不可能会变化你判断一次和判断N次结果都是一样的所以这个可以将判断条件提前加载。
var addEventHandler function(){if (window.addEventListener) {return function(el, sType, fn, capture) {el.addEventListener(sType, function(e) {fn.call(el, e);}, (capture));};} else if (window.attachEvent) {return function(el, sType, fn, capture) {el.attachEvent(on sType, function(e) {fn.call(el, e);});};}
}
var addEvent addEventHandler();
addEvent(document.body, click, function() {}, false);
addEvent(document.getElementById(test), click, function() {}, false);但是这样做还是有一种缺点因为我们无法判断程序中是否使用了这个方法但是依然不得不在文件顶部定义一下addEvent这样其实浪费了资源这里有一种更好的解决方法。
var addEvent function(el, sType, fn, capture){if (window.addEventListener) {addEvent function(el, sType, fn, capture) {el.addEventListener(sType, function(e) {fn.call(el, e);}, (capture));};} else if (window.attachEvent) {addEvent function(el, sType, fn, capture) {el.attachEvent(on sType, function(e) {fn.call(el, e);});};}
}在addEvent函数里面对其重新赋值这样既解决了每次运行都要判断的问题又解决了必须在作用域顶部执行一次造成浪费的问题。
React
在回家的路上我一直在想函数柯里化是不是可以扩展到更多场景我想把函数换成react组件试试我想到了高阶组件和redux的connect这两个确实是将柯里化思想用到react里面的体现。我们想一想如果把上面例子里面的函数换成组件参数换成高阶函数呢 var curry function(fn) {var func function() {var _args [].slice.call(arguments, 0);var func1 function() {[].push.apply(_args, arguments)return func1;}func1.toString func1.valueOf function() {return fn.apply(fn, _args);}return func1;}return func;
}var hoc function(WrappedComponent) {return function() {var len arguments.length;var NewComponent WrappedComponent;for (var i 0; i len; i) {NewComponent arguments[i](NewComponent)}return NewComponent;}
}
var MyComponent hoc(PageList);
curry(MyComponent)(addStyle)(addLoading)这个例子是对原来的PageList组件进行了扩展给PageList加了样式和loading的功能如果想加其他功能可以继续在上面扩展注意addStyle和addLoading都是高阶组件但是写法真的很糟糕一点都不coooooool我们可以使用compose方法underscore和loadsh这些库中已经提供了。
var enhance compose(addLoading, addStyle);
enhance(MyComponent)总结
其实关于柯里化的运用核心还是对函数闭包的灵活运用深刻理解闭包和作用域后就可以写出很多灵活巧妙的方法。