高级函数-函数柯里化

什么是柯里化

用于创建已经设置好了一个或多个参数的函数。

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

函数柯里化的基本方法和函数绑定是一样的:使用一个闭包返回一个函数。两者的区别在于,当函数被调用时,返回的函数还需要设置一些传入的参数。

柯里化函数通常调用另一个函数并为它传入要柯里化的函数和必要参数

第一版

// 将返回函数的参数进行排序
function curry(fn) {
    // 调用array.slice()方法,并传入参数1表示返回的数组包含从第二个参数开始的所有参数
    // args数组包含了来自外部函数的参数
    var args = Array.prototype.slice.call(arguments, 1);
    return function() {
        // innerArgs数组用来存放所有传入的参数
        var innerArgs = Array.prototype.slice.call(arguments);
        var finalArgs = args.concat(innerArgs);
        // 执行,不考虑执行环境
        return fn.apply(null, finalArgs);
    }
}

function add(num1, num2) {
    return num1 + num2
}
// 使用方式
var curriedAdd = curry(add, 3);
console.log(curriedAdd(4)); // 7

var curriedAdd = curry(add, 3, 4);
console.log(curriedAdd()); // 7

var curriedAdd = curry(add);
console.log(curriedAdd(3, 4)); // 7

第二版

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 || 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 的作用就是用函数包裹原函数,然后给原函数传入之前的参数,当执行 fn0(…)(…) 的时候,执行包裹函数,返回原函数,然后再调用 sub_curry 再包裹原函数,然后将新的参数混合旧的参数再传入原函数,直到函数参数的数目达到要求为止。

补充写法:

var curry = fn =>
    judge = (...args) =>
        args.length === fn.length
            ? fn(...args)
            : (arg) => judge(...args, arg)

使用场景

函数柯里化实现bind()函数

function bind(fn, context) {
    var args = Array.prototype.slice.call(arguments, 2);
    return function() {
        var innerArgs = Array.prototype.slice.call(arguments);
        var finalArgs = args.concat(innerArgs);
        return fn.apply(context, finalArgs);
    }
}

bind(…)可以对参数进行柯里化

简化差异性判断

如判断当前平台进行差异性操作,vue源码中的patch部分

经典面试题

请实现一个 add 函数,满足以下功能
add(1); 	// 1
add(1)(2);  	// 3
add(1)(2)(3)// 6
add(1)(2, 3);   // 6
add(1, 2)(3);   // 6
add(1, 2, 3);   // 6

参考链接:https://github.com/Advanced-Frontend/Daily-Interview-Question/issues/134

实现:

// 类似
function sum() {
    const args = [...arguments]
    const fn = function() {
        const newArgs = [...arguments]
        return sum.apply(null, args.concat(newArgs))
    }
    
    fn.valueOf = function() {
        return args.reduce((res, item) => {
            return res + item
        }, 0)
    }

    return fn
}

console.log(sum(1, 2, 3).valueOf())
console.log(sum(1)(2)(3).valueOf())

参考链接

浅谈函数柯里化

JavaScript专题之函数柯里化 - 柯里化概念及实现

深入高阶函数应用之柯里化