JS函数柯里化

函数柯里化

今天下午研究了一下函数柯里化,把我能看的困得呀,看完以后,总有种很亏的感觉,感觉浪费了两三个小时,去理解了一个不怎么用的东西,但是作为一个算法的了解,还是写篇博客吧,毕竟看了一下午。

什么是柯里化

比较官方的说法是,把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。

在直觉上,柯里化声称如果你固定某些参数,你将得到接受余下参数的一个函数。


我们先看一个非常简单的函数。

现在,我们要实现一个求三个数之和的函数:

function add(x, y, z) {
    return x + y + z;
}
console.log(add(1, 2, 3)); //6

这样一个简单的函数,我们可以通过一个柯里化的思想改变一下,只传入一个参数,然后返回的函数处理剩余的参数,可以改写成如下

let add = function(x) {
    return function(y) {
        return function(z) {
            return x + y + z;
        };
    };
}

let addFirst = add(1);
let addSecond = addFirst(2);
let addThird = addSecond(3);

console.log(addThird); // 6

利用闭包,可以轻易达成这样的效果。

那么接下来,我们将他优化一下。

//柯里化函数
let curryDelay = function(fn) {
    let _content = [];
    return function accu() {
        if (arguments.length === 0) {
            return fn(..._content);
        } else {
            _content.push(...Array.prototype.slice.call(arguments));
            return accu;
        }
    };
};

//求和函数
let add = function() {
    let sum = 0;
    for(let i of arguments) {
        sum += i;
    }
    return sum;
}
//柯里化
let addCurry = currying(add);
addCurry(1); //无返回值
addCurry(2); //无返回值
addCurry(3); //无返回值
addCurry(4); //无返回值
addCurry(); //10

//也可以连用
addCurry(1)(2)(3)(4)(); //10

可以看出一个求和函数被我们柯里化了,在执行完以后,他返回了一个函数,这个函数负责处理剩余的参数(后传进去的),而在不传参数之后,进行’结算’,将之前的所有参数的和输出。

柯里化的作用

这是我最头痛的,可能还是太菜,柯里化还是没有用到,希望之后能用到。
柯里化的作用大致有三个:
* 延迟执行
* 参数复用
* 动态生成函数

1.延迟执行

而我们上面的代码,就是柯里化一种延迟执行的用法,只要你执行中有参数,而且不论多少参数,他都会先存起来,等待你执行函数不带参数时,开始运算输出。就像是延迟了一样,这个在平时敲代码过程中,可能用到的是最少的。

2.参数复用

参数复用,当多次调用一个函数,并且传递的参数绝大多数是相同的时候,那么该函数就是一个很好的柯里化候选。
比如现在有这样一个函数

function print (name, subject, score) {
    let result = `${name}的${subject}得了${score}分`;
    console.log(result);
}

print('小明', '数学', '150'); //小明的数学得了150分
print('小明', '语文', '150'); //小明的语文得了150分
print('小明', '英语', '150'); //小明的英语得了150分

这里面参数name一直为相同的值,并且函数中也是一样的操作。
我们可以修改成如下

let currying = function(fn) {
    let _args = Array.prototype.slice.call(arguments, 1);
    return function() {
        let _newArgs = [..._args, ...Array.prototype.slice.call(arguments)];
        return fn(..._newArgs);
    };
};

//柯里化
let printCurry = curryAgain(print, '小明');
printCurry('数学', '150'); //小明的数学得了150分
printCurry('语文', '150'); //小明的语文得了150分
printCurry('英语', '150'); //小明的英语得了150分

同学们可能发现,代码变复杂了许多,而并没有多大用处,这我就要和大家说说了
因为我们写的例子很简单,函数操作不怎么复杂,而且需要复用的参数也只有一个,那么在平时的项目中,我们函数是很复杂的,参数复用是很。。多。。好像也不多,emmmmm好吧,你说的没错,就是没毛用

但是有一个最经典的却是用到了这个作用~就是bind的实现,后面会给出代码

3.动态创建函数

动态创建函数算是比较有用的吧,那么他可以减少一些判断操作(if, else),最经典的可能要属兼容浏览器的添加事件方法,在平常为了兼容IE(其实根本没兼容过这个智障浏览器),我们会这么写

const addEvent = function (el, type, fn, cature) {
    if (window.addEventListener) {
        el.addEventListener(type, (e) => fn.call(el, e), capture);
    } else if (window.attachEvent) {
        el.attachEvent('on' + type, (e) => fn.call(el, e);
    }
}

上面的代码有什么问题呢?很显然,我们每次使用它的时候,都会跑一边if…else…其实,用函数柯里化,我们只需要跑一遍,代码如下

const addEvent = (function(){
    if (window.addEventListener) {
        return function(el, type, fn, capture) {
            el.addEventListener(type, (e) => fn.call(el, e), capture);
        };
    } else if (window.attachEvent) {
        return function(el, type, fn, capture) {
            el.attachEvent('on' + type, (e) => fn.call(el, e);
        };
    }
})();

这样写,我们只需要每次在代码的前方声明一下,就可以使用这个函数了,剩余的参数应用都是返回的函数来实现的


4.经典操作

柯里化在平时的应用是少的可怜,有一个最经典的实现bind,我大概说一下

Function.prototype.bind = function(context) {
    let me = this;
    let args = Array.prototype.slice.call(arguments, 1);
    return function() {
        let innerArgs = Array.prototype.slice.call(arguments);
        let finalArgs = args.concat(innerArgs);
        return me.apply(context, finalArgs);
    }
}

这就是bind的一个简易实现,他借用call和apply很方便的实现了bind。当然原本的方法肯定不是这样的,考虑的东西要更多。

最后,我觉得柯里化更像是一种闭包的应用,其中还有apply和call的各种用法,虽然平时不怎么用到,但确实是一种提高能力的算法,还是值得研究一番的。

你可能感兴趣的:(JavaScript正常操作)