参考文章
柯理化
概念
- 柯里化:将接受多个参数的函数转成接受单一参数的函数,并返回接受余下参数而且返回结果的新函数的技术
- 柯里化其实是函数式编程的一个过程,在这个过程中我们能把一个带有多个参数的函数转换成一系列的嵌套函数。他返回一个新函数,这个新函数期望传入下一个参数。
- 他不断的返回新函数,直到所有的参数都被使用,参数会一直保持alive(通过闭包),当柯里化函数链中最后一个函数被返回和调用的时候,所有的参数会被用于执行。
- 柯里化把一个多参数函数转换成一系列只带单个参数的函数
也就是只传递函数的一部分参数来调用他,让他返回一个函数去处理剩下的参数
function add(x, y){ return x + y; }
function curryingAdd(x){
return function (y) {
return x + y;
}
}
add(1,2);
curryingAdd(1)(2);
这样做的好处
- 1.参数复用
- 这个示例是一个正则校验,正常来说直接调用check函数就可以了,但是如果在很多地方都要校验是否有数字,其实就是需要将第一个参数reg进行复用,这样别的地方就能直接调用hasNumber/hasLetter等函数,让参数能够复用,调用起来更方便
function check(reg, txt){ return reg.test( txt ); }
check(/\d+/g, 'aaa');
check(/[a-z]+/g, 'aaa');
function curryingCheck(reg){
return function(txt){
return reg.test(txt)
}
}
var hasNumber = curryingCheck(/\d+/g);
var hasLetter = curryingCheck(/[a-z]+/g);
hasNumber('test1');
hasNumber('testtest');
hasLetter('12112');
- 2.提前确认
- 我们在做项目的过程中,封装一些dom操作可以说再常见不过,上面第一种写法也是比较常见,但是我们看看第二种写法,它相对一第一种写法就是自执行然后返回一个新的函数,这样其实就是提前确定了会走哪一个方法,避免每次都进行判断。
var on = function (element, event, handler){
if(document.addEventListener){
if(element && event && handler){
element.addEventListener(event, handler);
}
}else{
if(element && event && handler){
element.attachEvent('on' + event, handler);
}
}
};
var on = (function(){
if(document.addEventListener){
return function (element, event, handler){
if(element && event && handler){
element.addEventListener(event, handler);
}
}
}else{
return function(element, event, handler){
if(element, event, handler){
element.attachEvent('on' + event, handler);
}
}
}
})();
var on = function(isSupport, element, event, handler) {
isSupport = isSupport || document.addEventListener;
if (isSupport) {
return element.addEventListener(event, handler, false);
} else {
return element.attachEvent('on' + event, handler);
}
}
- 3.延迟运行
- 像js中经常使用的bind,实现的机制就是currying
Function.prototype.bind = function(context){
var _this = this;
var args = Array.proptotype.slice.call(arguments, 1);
return function() {
return _this.apply(context, args);
}
}
将多参数函数转换成柯里化函数的通用的封装方法
function currying(fn){
const inner = (args = []) => {
return args.length >= fn.length? fn(...args) : (...userArgs) => inner([...args, ...userArgs])
}
return inner()
}
无限参数的柯里化
- 在前端面试中,你可能会遇到这样一个涉及到柯里化的题目。
add(1)(2)(3) = 6;
add(1, 2, 3)(4) = 10;
add(1)(2)(3)(4)(5) = 15;
- 这个题目的目的是想让add执行之后返回一个函数能够继续执行,最终运算的结果是所有出现过的参数之和。而这个题目的难点则在于参数的不固定。我们不知道函数会执行几次。因此我们不能使用上面封装的通用公式来转换一个柯里化函数。只能自己封装,那么怎么办呢?在此之前,补充2个非常重要的知识点。
1. es6的不定参数
- 假如我们有一个数组,希望把这个数组中所有的子项展开传递给一个函数作为参数。那么我们应该怎么做?
function add(a, b, c, d) {
return a + b + c + d;
}
var args = [1, 3, 100, 1];
在ES5中,我们可以借助之前学过的apply来达到我们的目的。
add.apply(null, args);
而在ES6中,提供了一种新的语法来解决这个问题,那就是不定参。写法如下:
add(...args);
这两种写法是等效的。OK,先记在这里。在接下的实现中,我们会用到不定参数的特性。
2.函数的隐式转换
当我们直接将函数参与其他的计算时,函数会默认调用toString方法,直接将函数体转换为字符串参与计算。
function fn() { return 20 }
console.log(fn + 10);
我们可以重写函数的toString方法,让函数参与计算时,输出我们想要的结果。
function fn() { return 20; }
fn.toString = function() { return 30 }
console.log(fn + 10);
除此之外,当我们重写函数的valueOf方法也能够改变函数的隐式转换结果。
function fn() { return 20; }
fn.valueOf = function() { return 60 }
console.log(fn + 10);
当我们同时重写函数的toString方法与valueOf方法时,最终的结果会取valueOf方法的返回结果。
function fn() { return 20; }
fn.valueOf = function() { return 50 }
fn.toString = function() { return 30 }
console.log(fn + 10);
- 补充了这两个知识点之后,我们可以来尝试完成之前的题目了。add方法的实现仍然会是一个参数的收集过程。当add函数执行到最后时,仍然返回的是一个函数,但是我们可以通过定义toString/valueOf的方式,让这个函数可以直接参与计算,并且转换的结果是我们想要的。而且它本身也仍然可以继续执行接收新的参数。实现方式如下。
function add() {
var _args = [].slice.call(arguments);
var adder = function () {
var _adder = function() {
_args.push(...arguments);
return _adder;
};
_adder.toString = function () {
return _args.reduce(function (a, b) {
return a + b;
});
}
return _adder;
}
return adder(..._args);
}
var a = add(1)(2)(3)(4);
var b = add(1, 2, 3, 4);
var c = add(1, 2)(3, 4);
var d = add(1, 2, 3)(4);
console.log(a + 10);
console.log(b + 20);
console.log(c + 30);
console.log(d + 40);
console.log(a(10) + 100);
console.log(b(10) + 100);
console.log(c(10) + 100);
console.log(d(10) + 100);
function add(...args) {
return args.reduce((a, b) => a + b);
}