带你看懂javascript函数柯里化(currying)

1、什么是柯里化

这里参照百度百科:

在计算机科学中,柯里化(Currying)是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且返回结果的新函数的技术。

一句话概述:

柯里化是一种将使用多个参数的一个函数转换成一系列使用一个参数的函数的技术

函数柯里化有什么用:
我这里放几张知乎大佬的回答:

带你看懂javascript函数柯里化(currying)_第1张图片
带你看懂javascript函数柯里化(currying)_第2张图片
带你看懂javascript函数柯里化(currying)_第3张图片
带你看懂javascript函数柯里化(currying)_第4张图片

是不是还是搞不懂它是什么意思,没关系我也不懂,我们接着往下看,

2、一个简单的柯里化函数

function sum (a, b, c) {
    console.log(a + b + c);
}
sum(1, 2, 3); // 6

毫无疑问,sum 是个简单的累加函数,接受3个参数,输出累加的结果。

假设有这样的需求,sum的前2个参数保持不变,最后一个参数可以随意。那么就会想到,在函数内,是否可以把前2个参数的相加过程,给抽离出来,因为参数都是相同的,没必要每次都做运算。

如果先不管函数内的具体实现,调用的写法可以是这样: sum(1, 2)(3); 或这样 sum(1, 2)(10); 。就是,先把前2个参数的运算结果拿到后,再与第3个参数相加。

这其实就是函数柯里化的简单应用。

总结来说:柯里化的目的是,减少代码冗余,以及增加代码的可读性

3、柯里化的好处

1、参数复用
2、提前确认
3、延迟运行

1.参数复用

//上面的示例是一个正则的校验,正常来说直接调用check函数就可以了,
//但是如果我有很多地方都要校验是否有数字,
//其实就是需要将第一个参数reg进行复用,
//这样别的地方就能够直接调用hasNumber,hasLetter等函数,
//让参数能够复用,调用起来也更方便。

// 正常正则验证字符串 reg.test(txt)

// 函数封装后
function check(reg, txt) {
    return reg.test(txt)
}

check(/\d+/g, 'test')       //false
check(/[a-z]+/g, 'test')    //true

// Currying后
function curryingCheck(reg) {
    return function(txt) {
        return reg.test(txt)
    }
}

var hasNumber = curryingCheck(/\d+/g)
var hasLetter = curryingCheck(/[a-z]+/g)

hasNumber('test1')      // true
hasNumber('testtest')   // false
hasLetter('21212')      // false



2、提前确认

我们在做项目的过程中,封装一些dom操作可以说再常见不过,
上面第一种写法也是比较常见,但是我们看看第二种写法,
它相对一第一种写法就是自执行然后返回一个新的函数,
这样其实就是提前确定了会走哪一个方法,避免每次都进行判断。
var on = function(element, event, handler) {
    if (document.addEventListener) {
        if (element && event && handler) {
            element.addEventListener(event, handler, false);
        }
    } 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, false);
            }
        };
    } else {
        return function(element, event, handler) {
            if (element && event && handler) {
                element.attachEvent('on' + event, handler);
            }
        };
    }
})();

//换一种写法可能比较好理解一点,上面就是把isSupport这个参数给先确定下来了
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.prototype.slice.call(arguments, 1)
 
    return function() {
        return _this.apply(context, args)
    }
}

4、封装柯里化(重点)

function curry (fn, currArgs) {
    return function() {
        let args = [].slice.call(arguments);

        // 首次调用时,若未提供最后一个参数currArgs,则不用进行args的拼接
        if (currArgs !== undefined) {
            args = args.concat(currArgs);
        }

        // 递归调用
        if (args.length < fn.length) {
            return curry(fn, args);
        }

        // 递归出口
        return fn.apply(null, args);
    }
}
//这样就可以直接调用curry了

解析:

首先,它有 2 个参数,fn 指的就是本文一开始的源处理函数 sum。currArgs 是调用 curry 时传入的参数列表,比如 (1, 2)(3) 这样的。
再看到 curry 函数内部,它会整个返回一个匿名函数。
再接下来的 let args = [].slice.call(arguments);,意思是将 arguments 数组化。arguments 是一个类数组的结构,它并不是一个真的数组,所以没法使用数组的方法。我们用了 call 的方法,就能愉快地对 args 使用数组的原生方法了。
currArgs !== undefined 的判断,是为了解决递归调用时的参数拼接。
最后,判断 args 的个数,是否与 fn (也就是 sum )的参数个数相等,相等了就可以把参数都传给 fn,进行输出;否则,继续递归调用,直到两者相等。

来测试一下封装的curry:

function sum(a, b, c) {
    console.log(a + b + c);
}

const fn = curry(sum);

fn(1, 2, 3); // 6
fn(1, 2)(3); // 6
fn(1)(2, 3); // 6
fn(1)(2)(3); // 6

5、柯里化面试考点

1、柯里化性能

1、存取arguments对象通常要比存取命名参数要慢一点
2、一些老版本的浏览器在arguments.length的实现上是相当慢的
3、使用fn.apply( … ) 和 fn.call( … )通常比直接调用fn( … ) 稍微慢点
4、创建大量嵌套作用域和闭包函数会带来花销,无论是在内存还是速度上

其实在大部分应用中,主要的性能瓶颈是在操作DOM节点上,这js的性能损耗基本是可以忽略不计的,所以curry是可以直接放心的使用。

2、代码题

// 实现一个add方法,使计算结果能够满足如下预期:
add(1)(2)(3) = 6;
add(1, 2, 3)(4) = 10;
add(1)(2)(3)(4)(5) = 15;

function add() {
    // 第一次执行时,定义一个数组专门用来存储所有的参数
    var _args = Array.prototype.slice.call(arguments);

    // 在内部声明一个函数,利用闭包的特性保存_args并收集所有的参数值
    var _adder = function() {
        _args.push(...arguments);
        return _adder;
    };

    // 利用toString隐式转换的特性,当最后执行时隐式转换,并计算最终的值返回
    _adder.toString = function () {
        return _args.reduce(function (a, b) {
            return a + b;
        });
    }
    return _adder;
}

add(1)(2)(3)                // 6
add(1, 2, 3)(4)             // 10
add(1)(2)(3)(4)(5)          // 15
add(2, 6)(1)                // 9

总结:函数的柯里化,是 Javascript 中函数式编程的一个重要概念。它返回的,是一个函数的函数。其实现方式,需要依赖参数以及递归,通过拆分参数的方式,来调用一个多参数的函数方法,以达到减少代码冗余,增加可读性的目的。
前端面试题:柯里化函数总结

参考文章:
柯里化对函数式编程有何意义?
理解函数的柯里化
详解js函数柯里化

你可能感兴趣的:(javascript,jquery,javascript,js)