什么是js函数的currying/柯里化?

2016-10-24 1219 2 编辑:深色多郎 来源:程序设计书籍

说到js的柯里化,相信很多朋友都会头大。或者不是很清楚。我今天简单的给大家介绍一下!我用一句话总结函数柯里化,js柯里化是逐步传参,逐步缩小函数的适用范围,逐步求解的过程。

可能对这句话你不是很清楚,那么,我们来看个案例,简单说明一下:

需求:我们写一个函数,将函数的几个参数相加,返回结果!那我们写的函数如下

var concat3Words = function (a, b, c) {

    return a+b+c;

};

函数柯里化呢?是分部求解,先传一个a参数,再传一个b参数,再传一个c参数,最后将这三个参数相加!

var concat3WordsCurrying = function(a) {

    return function (b) {

        return function (c) {

            return a+b+c;

        };

    };

};

我们看输出结果:

console.log(concat3Words("foo ","bar ","baza"));            // foo bar baza

console.log(concat3WordsCurrying("foo "));                  // [Function]

console.log(concat3WordsCurrying("foo ")("bar ")("baza"));  // foo bar baza 函数链式调用

柯里化案例

上面的需求我们稍微变一下。现在我们更进一步,如果要求可传递的参数不止3个,可以传任意多个参数,当不传参数时输出结果?那现在我们用柯里化来简单的实现一下:

var adder = function () {

    var _args = [];

    return function () {

        if (arguments.length === 0) {

            return _args.reduce(function (a, b) {

                return a + b;

            });

        }

        [].push.apply(_args, [].slice.call(arguments));

        return arguments.callee;

    }

};    

var sum = adder();

console.log(sum);     // Function

sum(100,200)(300);    // 调用形式灵活,一次调用可输入一个或者多个参数,并且支持链式调用

sum(400);

console.log(sum());   // 1000 (加总计算) 

上面 adder是柯里化了的函数,它返回一个新的函数,新的函数接收可分批次接受新的参数,延迟到最后一次计算。我们可以任意传入参数,当不传参数的时候,输出计算结果!

基础知识普及之arguments

看到上面的柯里化实现,可能有的朋友会有点晕,其实上面也没有什么新的知识,假如你看过我之前写的文章,相信能够理解!apply,call之前都有介绍。前端干货中的第四条,也有提及!唯独arguments前面文章没有介绍。

arguments对象是比较特别的一个对象,实际上是当前函数的一个内置属性。arguments非常类似Array,但实际上又不是一个Array实例。

我们看一下下面的例子:

function f(a, b, c){

    alert(arguments.length);   // result: "2"

    a = 100;

    alert(arguments[0]);       // result: "100"

    arguments[0] = "haorooms";

    alert(a);                  // result: "haorooms"

    alert(c);                  // result: "undefined"

    c = 2016;

    alert(arguments[2]);       // result: "undefined"

}

f(1, 2);

我们通常用

[].slice.call(arguments, 1)

将传入参数转为数组。slice是js的一个函数,关于其用法,不会的去查一下!上面的写法相当于Array.prototype.slice.call(arguments, 1);

另外,arguments对象中有一个非常有用的属性:callee。arguments.callee返回此arguments对象所在的当前函数引用。在使用函数递归调用时推荐使用arguments.callee代替函数名本身。arguments就介绍到这里!

通用的柯里化函数

下面这个是通用的柯里化函数

var currying = function (fn) {

    var _args = [];

    return function () {

        if (arguments.length === 0) {

            return fn.apply(this, _args);

        }

        Array.prototype.push.apply(_args, [].slice.call(arguments));

        return arguments.callee;

    }

};

我们可以通过如下函数简单应用一下柯里化:

var multi=function () {

    var total = 0;

    for (var i = 0, c; c = arguments[i++];) {

        total += c;

    }

    return total;

};

var sum = currying(multi);  

sum(100,200)(300);

sum(400);

console.log(sum());     // 1000  (空白调用时才真正计算)

增加适用性的柯里化

上面我的介绍的是柯里化延迟执行,缩小范围!还有一种是增加了函数的适用性,但同时也降低了函数的适用范围。其通用写法如下:

function currying(fn) {

    var slice = Array.prototype.slice,

    __args = slice.call(arguments, 1);

    return function () {

        var __inargs = slice.call(arguments);

        return fn.apply(null, __args.concat(__inargs));

    };

}

例如:

function square(i) {

    return i * i;

}

function map(handeler, list) {

    return list.map(handeler);

}

// 数组的每一项平方

map(square, [1, 2, 3, 4, 5]);

map(square, [6, 7, 8, 9, 10]);

map(square, [10, 20, 30, 40, 50]);

例子中,创建了一个map通用函数,用于适应不同的应用场景。显然,通用性不用怀疑。同时,例子中重复传入了相同的处理函数:square。

柯里化改造:

function square(i) {

    return i * i;

}

function map(handeler, list) {

    return list.map(handeler);

}

var mapSQ = currying(map, square);

mapSQ([1, 2, 3, 4, 5]);

mapSQ([6, 7, 8, 9, 10]);

mapSQ([10, 20, 30, 40, 50]);

Function.prototype.bind 方法也是柯里化应用

与 call/apply 方法直接执行不同,bind 方法 将第一个参数设置为函数执行的上下文,其他参数依次传递给调用方法(函数的主体本身不执行,可以看成是延迟执行),并动态创建返回一个新的函数, 这符合柯里化特点。

var foo = {x: 888};

var bar = function () {

    console.log(this.x);

}.bind(foo);               // 绑定

bar();  

关于函数柯里化就说到这里。与函数柯里化相对于的是反柯里化。


本站文章均为深正网站建设摘自权威资料,书籍,或网络原创文章,如有版权纠纷或者违规问题,请即刻联系我们删除,我们欢迎您分享,引用和转载,但谢绝直接搬砖和抄袭!感谢...
关注深正互联
我们猜你喜欢