Better

Ethan的博客,欢迎访问交流

JavaScript函数柯里化

听说这玩意很牛逼,很想好好理解并懂得应用场景。

技术准备

splice和slice区别

  • splice

    • 作用:splice() 方法用于插入、删除或替换数组的元素。
    • 语法:arrayObject.splice(index,howmany,element1,.....,elementX)
    • 返回值:如果从 arrayObject 中删除了元素,则返回的是含有被删除的元素的数组。
    • 注意事项:splice() 方法会直接对数组进行修改。
  • slice

    • 作用:slice() 方法可提取字符串的某个部分,并以新的字符串返回被提取的部分。对于数组是同样的。
    • 语法:stringObject.slice(start,end)
    • 返回值:一个新的字符串。
    • 注意事项:slice() 方法不会直接对数组进行修改。

柯里化用途

在数学和计算机科学中,柯里化是一种将使用多个参数的一个函数转换成一系列使用一个参数的函数的技术。

curry 的这种用途可以理解为:参数复用。本质上是降低通用性,提高适用性

如果我们仅仅是把参数一个一个传进去,意义可能不大,但是如果我们是把柯里化后的函数传给其他函数比如 map 呢?

比如我们有这样一段数据:

var person = [{name: 'kevin'}, {name: 'daisy'}]

如果我们要获取所有的 name 值,我们可以这样做:

var name = person.map(function (item) {
    return item.name;
})

不过如果我们有 curry 函数:

var prop = curry(function (key, obj) {
    return obj[key]
});

var name = person.map(prop('name'))

是不是又麻烦了些?但是要注意,prop 函数编写一次后,以后可以多次使用,实际上代码从原本的三行精简成了一行,而且你看代码是不是更加易懂了?

具体实现

先上大神代码

function sub_curry(fn) {
    var args = [].slice.call(arguments, 1);
    return function() {
        return fn.apply(this, args.concat([].slice.call(arguments)));
    };
}

function curry(fn, length) {
    // 函数的length指参数的个数
    length = length || fn.length;

    var slice = Array.prototype.slice;

    return function() {
        if (arguments.length < length) {
            var combined = [fn].concat(slice.call(arguments));
            return curry(sub_curry.apply(this, combined), length - arguments.length);
        } else {
            return fn.apply(this, arguments);
        }
    };
}

var fn = curry(function(a, b, c) {
    return [a, b, c];
});

fn("a", "b", "c") // ["a", "b", "c"]
fn("a", "b")("c") // ["a", "b", "c"]
fn("a")("b")("c") // ["a", "b", "c"]
fn("a")("b", "c") // ["a", "b", "c"]

实现目标参数可以单个或多个分开传,原理就是如果参数没有达到个数要求,就用函数(sub_curry)包裹原函数,然后给原函数传入之前的参数,执行包裹函数,返回原函数,然后再调用 sub_curry 再包裹原函数,然后将新的参数混合旧的参数再传入原函数,直到函数参数的数目达到要求为止。

在上面的例子中,第一次执行fn,函数为原函数,之后递归执行curry之后的fn都是被包裹的原函数!

// 第一次curry后的fn
ƒ (a, b, c) {
    return [a, b, c];
}
// 递归执行curry之后的fn
ƒ () {
    return fn.apply(this, args.concat([].slice.call(arguments)));
}

更易懂的实现:

function curry(fn, args) {
    length = fn.length;
    args = args || [];
    return function() {
        var _args = args.slice(0),
            arg, i;
        for (i = 0; i < arguments.length; i++) {
            arg = arguments[i];
            _args.push(arg);
        }
        if (_args.length < length) {
            return curry.call(this, fn, _args);
        }
        else {
            return fn.apply(this, _args);
        }
    }
}

来源



留言