【js基础】函数柯理化的实现

2020-08-28  本文已影响0人  SophieRabbit

函数式编程

与面向对象编程(Object-oriented programming)和过程式编程(Procedural programming)并列的编程范式。

最主要的特征是,函数是第一等公民。

强调将计算过程分解成可复用的函数,典型例子就是map方法和reduce方法组合而成 MapReduce 算法。

只有纯的、没有副作用的函数,才是合格的函数。

为什么函数式编程要求函数必须是纯的,不能有副作用?因为它是一种数学运算,原始目的就是求值,不做其他事情,否则就无法满足函数运算法则了。

总之,在函数式编程中,函数就是一个管道(pipe)。这头进去一个值,那头就会出来一个新的值,没有其他作用。

函数式编程有两个最基本的运算:合成和柯里化。

合成

如果一个值要经过多个函数,才能变成另外一个值,就可以把所有中间步骤合并成一个函数,这叫做"函数的合成"(compose)。

const compose = function (f,g) {

    return function(x) {

        return f(g(x));

    };

}

有一个函数,接收函数F作为参数,运行后能够返回一个新的函数。并且这个新的函数能够处理函数F的剩余参数。

f(x)和g(x)合成为f(g(x)),有一个隐藏的前提,就是f和g都只能接受一个参数。如果可以接受多个参数,比如f(x, y)和g(a, b, c),函数合成就非常麻烦。

这时就需要函数柯里化了。所谓"柯里化",就是把一个多参数的函数,转化为单参数函数,并且返回接受余下的参数而且返回结果的新函数的技术。

function add(a,b,c) {

    return a+b+c;

}

经过柯理化后的结果:每次只需要传入一个参数了

function _add(a) {

    return function(b) {

        return function(c) {

            return a + b + c;

        }

    }

}

以上代码可以使得add(1,2,3)与_add(1)(2)(3)变得等价。但在传参方面,变为可以分多步传参。

柯理化的通用范式

当然,靠眼力封装的柯里化函数自由度偏低,柯里化通用式具备更加强大的能力。因此我们需要知道如何去封装这样一个柯里化的通用式。

// 简单实现,参数只能从右到左传递

function createCurry(func, args) {    // func需要处理的函数, args是func函数的部分参数

    var funLen = func.length;    // 原始函数的参数的长度

    var args = args || [];    // 后续的参数们

    return function() {   // 这里的arguments

        // 改arguments为数组( [].slice.call() 常用来将类数组转化为真正的数组 )

        var _args = [].slice.call(arguments);     // 这里的_args为: []

        [].push.apply(_args, args);    // 等价于_args.push(...args), [].push.apply是为了防止报错

        // [].push.apply:  https://blog.csdn.net/qq_29055201/article/details/84972285

        // 如果参数个数小于最初的func.length,则递归调用,继续收集参数

        if (_args.length < funLen) {     // 当前参数个数不全

            return createCurry.call(this, func, _args);   

            // xxx.call(this)使用func对象代替this对象,返回一个柯理化函数等待下次参数补全

        }

        // 参数收集完毕,则执行func

        return func.apply(this, _args);

    }

}

柯理化的案例:

function check(targetString, reg) {    // 这是一个返回正则的函数

    return reg.test(targetString);

}

var _check = createCurry(check);   // 返回一个柯理化后的函数,可以后续传参生成函数;

var checkPhone = _check(/^1[34578]\d{9}$/);  

var checkEmail = _check(/^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/);

checkPhone('183888888');   // 很好用

checkEmail('xxxxx@test.com');

继续来思考一个例子。这个例子与map有关。在高阶函数的章节中,我们分析了封装map方法的思考过程。由于我们没有办法确认一个数组在遍历时会执行什么操作,因此我们只能将调用for循环的这个统一逻辑封装起来,而具体的操作则通过参数传入的形式让使用者自定义。这就是map函数。

参考链接:

http://www.ruanyifeng.com/blog/2017/02/fp-tutorial.html

https://www.jianshu.com/p/5e1899fe7d6b

上一篇下一篇

猜你喜欢

热点阅读