函数反柯里化

1,531 阅读2分钟

这是我参与8月更文挑战的第3天,活动详情查看: 8月更文挑战

前言

最近在看《JavaScript设计模式与开发实践》这本书,在里边看到了一个反柯里化的概念。反柯里化??以前在学习函数式编程的时候只接触过柯里化,反柯里化还真是不清楚说的啥。

在说反柯里化之前,先来复习下柯里化的基础。

柯里化

函数柯里化在前边函数式编程基础中已经提过了,它主要是把多元函数转化成一元函数。将接受多个参数的函数变换成一个单一参数的函数,并且返回一个接受剩余的参数的函数。主要用来参数复用,延迟计算,提前返回等。

柯里化的实现有很多种,看一个简单的柯里化函数的实现代码:

function curry(fun) {
    return function curried() {
        let args = Array.prototype.slice.call(arguments);  //保存当前输入的所有参数
        if(arguments.length <  fun.length) {     //还在继续输入
            return function() {
                let innerArgs = Array.prototype.slice.call(arguments);
                let allParams = args.concat(innerArgs);
                return curried.apply(this, allParams);
            }
        }else {
            return fun.apply(this , args);
        }
    }
}

上述柯里化的代码主要用来做延迟求值。在参数没输入完的时候它不会去执行fun函数,而是使用闭包将参数缓存下来,同时返回一个新函数。等到函数输入完,再一次性求值。

这里用到了函数的length属性,所以在使用的时候不要用ES6的剩余参数语法,也不要给参数默认值。

反柯里化

那什么是反柯里化呢??是与柯里化相反吗??

书中对反柯里化的作用做了介绍:

反柯里化是为了扩大函数的适用性,使本来作为特定对象所拥有的功能的函数可以被任意对象所用。

直接看这段文字,好像不太理解具体啥含义,看一下书里给的反柯里化的代码:

Function.prototype.uncurrying = function() {
    var self = this;   //self为Array.prototype.push
    return function() {
        //obj = {0:1, length: 1}, arguments = [2, callee: ƒ, Symbol(Symbol.iterator): ƒ]
        var obj = Array.prototype.shift.call(arguments); 
        
        //Array.ptototype.push(obj, 2)
        return self.apply(obj, arguments);
    }
}
var testObj = {
    length: 1,
    0: 1
}
var push = Array.prototype.push.uncurrying();
push(testObj, 2);
console.log(testObj);   //{0: 1, 1: 2, length: 2}

从代码可以可以看到,push方法本来是只能用在数组中的,现在通过反柯里化让对象也可以用push方法了。push函数变成了一个通用的函数,不再局限于Array了。

网上针对反柯里化有个通用函数,可以来看一下:

Function.prototype.unCurrying = function() {
    let self = this;
    return function() {
        return Function.prototype.call.apply(self, arguments);
    }
}

乍一看,这段代码还是有点难理解,又是call又是apply的。同样拿上边的push函数来测试一下:

  • 首先apply函数接收两个参数,第一个是函数体内this对象的指向,第二个是一个数组或类数组的集合。这里的arguments是一个类数组[{0: 1, 1: 2, length: 2}, 2, callee: ƒ, Symbol(Symbol.iterator): ƒ],即[testObj, 2],self为self为Array.prototype.push

  • 接下来执行call函数,call函数的第一个参数同样是改变this指向的,第二个参数开始都为传入的的参数。所以这段代码就类似于Array.prototype.push.call(testObj, 2)

现在大概明白了,call和apply主要是为了处理参数和this的问题。

总体来说,反柯里化增加了方法的适用范围,增加了函数的通用性。

推荐&&参考

Javascript中有趣的反柯里化技术