柯里化理解笔记

前言:

最近在看Taro文档,里面提到了js函数的柯里化,完全不懂。搜了写资料后,大概理解了点意思,记录一下心得
参考:前端基础进阶(八):深入详解函数的柯里化

1. 作用:

将传统多参函数转换为链式函数调用,方便对函数进行拆分封装。

2.核心:

利用js闭包捕获每次链式调用的参数并储存,最后一次调用时,对原函数进行apply调用

3.举例:

假设有个工厂(函数)可以放入样板,原材料(参数),生产出成品衣服(结果)。传统函数将样板,材料a,材料b均作为参数传给工厂,生成产品。柯里化后,该工厂可以先接收样板,升级为新的工厂(衣服工厂,裤子工厂),接着再将材料传给该工厂后,最终输出产品。在传输完材料前,工厂返回的就是含有部分材料的新工厂(中间函数),只有全部材料全部传递完成或者下达生产命令(变参函数)后,才能返回产品。

思考题:

实现add()方法,使得以下调用形式:

add(1,2,3,4)
add(1,2)(3,4)
add(1,2,3)(4)
add(1)(2)(3)(4)

均输出结果10


简化思考:

普通相加函数:
function nomalAdd(...params)
{
    return params.reduce((a,b) => a+b)
}

简单链式:
function adder(...firstParam)
{
    var result = nomalAdd(...firstParam)
    return function(...secondParams)
    {
        result += nomalAdd(...secondParams)
        return result
    }
}
var result = adder(1,2)(3,4)
console.log(result)    //10

可以看到,简单链式函数 其实就是两个嵌套函数,外层函数adder()返回的是一个函数,并通过闭包的特性,将第一次计算的结果传递给闭包储存起来,第二次调用时候就能得出正确结果。这就是最简单的柯里化思想,每次链式调用,均返回内部闭包函数,并将上一步结果储存,最后返回正确结果,如果每次储存的不是上一步的结果,而是将每一次调用的参数储存起来,最终将全部参数应用于原始函数,这就是完整的柯里化思维。

函数的柯里化

对于传统多参数函数,通过某个方法将其转换为可以链式调用的函数,称为函数的柯里化,本质上就是上面所说。缺点是刚开始看不太容易理解,很容易疑惑为什么折腾一圈后,最终还是使用了原函数通过apply传入原始参数来调用。优点是可以将函数拆分,方便进行二次封装。

1.定参函数(函数参数个数固定):

function add(a,b,c)
{
    return a+b+c
}

//封装的柯里化函数
function createCurry(func)
{
    var funcParamCount = func.length//函数的length返回函数参数个数
    var savedArgs = []//用来储存函数
    var innerFunc = function(...newArgs)//返回的闭包函数,用来链式调用
    {
        savedArgs.push(...newArgs)//将新的链式调用的参数加入到保存函数的数组中
        if (savedArgs.length == funcParamCount)//如果储存的数组个数等于原始函数的参数个数,说明链式调用结束
        {
            return func.apply(this, savedArgs)//使用apply,将保存的所有函数传递给原始函数调用,返回结果
        }
        else
        {
            return innerFunc//否则说明链式调用未结束,将闭包函数自身返回回去
        }
    }
    return innerFunc//柯里化时,生成的就是该闭包函数
}
//使用
var adder = createCurry(add)//柯里化生成adder函数
var res = adder(1)(2)(3)//链式调用
console.log(res)        //6
var midderAdder1 = adder(1)//调用完成前,中间值均为adder闭包函数
var midderAdder2 = adder(1)(2)
console.log(typeof(midderAdder))//function
console.log(midderAdder1 == midderAdder2)//true

2.不定参函数(函数参数个数不确定):

function add(...args) {
    return args.reduce((a,b) => 
    {
        return a+b
    }) 
}
//封装的柯里化函数
function createCurry(func)
{
    var funcParamLength = func.length//依然记录原始函数的个数,不定参函数时为0
    var savedArgs = []
    var innerFunc = (...newArgs) => {
        if (newArgs.length > 0)
        {
            savedArgs.push(...newArgs)
        }
        if (funcParamLength > 0)//定参函数
        {
            if (savedArgs.length < funcParamLength)
            {
                return innerFunc
            }
            else
            {
                var result = func.apply(this, savedArgs)
                savedArgs = []//清空保存的参数
                return result
            }
        }
        else//不定参函数
        {
            if (newArgs.length > 0)//链式调用在继续
            {
                return innerFunc
            }
            else//认为链式调用结束
            {
                var result = func.apply(this, savedArgs)
                savedArgs = []
                return result
            }
        }
    }
    return innerFunc
}
//调用
var adder = createCurry(add)
var result = adder(1,2)(4,5)
console.log(typeof(result)) //function
console.log(adder == result)//true
console.log(result())       //12

因为不定参函数需要确定在何时停止链式调用,返回结果,所以默认为不传入参数调用时,链式调用结束。同时这也是上面思考题的解法,柯里化后的adder函数,就是答案:

var adder = createCurry(add)
console.log(adder(1,2,3,4)())   //10
var adder = createCurry(add)
console.log(adder(1,2)(3,4)())  //10
var adder = createCurry(add)
console.log(adder(1,2,3)(4)())  //10
var adder = createCurry(add)
console.log(adder(1)(2)(3)(4)())//10

有缺陷在于,链式调用之后需要在调用一次空参数才能输出正确结果,否则则输出function,这是因为链式调用的中间值为函数对象,可以给函数对象添加toString()方法,即可在打印时,作为number类型输出:

var aFunc = function () {
    console.log('I am a function')
}
aFunc.toString = function() {
    return "I am a function description"
}

console.log(aFunc)          //I am a function description
aFunc()                     //I am a function
console.log(typeof(aFunc))  //function

因此稍微修改createCurry()方法

//修改createCurry
function createCurry(func)
{
    var funcParamLength = func.length//依然记录原始函数的个数,不定参函数时为0
    var savedArgs = []
    var innerFunc = (...newArgs) => {
        if (newArgs.length > 0)
        {
            savedArgs.push(...newArgs)
        }
        if (funcParamLength > 0)//定参函数
        {
            if (savedArgs.length < funcParamLength)
            {
                return innerFunc
            }
            else
            {
                var result = func.apply(this, savedArgs)
                savedArgs = []//清空结果
                return result
            }
        }
        else//不定参函数
        {
            if (newArgs.length > 0)//链式调用在继续
            {
                return innerFunc
            }
            else//认为链式调用结束
            {
                var result = func.apply(this, savedArgs)
                savedArgs = []
                return result
            }
        }
    }
    //添加toString()方法,输出结果
    innerFunc.toString = () => {
        var result = func.apply(this, savedArgs)
        savedArgs = []
        return result
    }
    return innerFunc
}
调用:
var adder = createCurry(add)
var result = adder(1)(2)(3,4)   //f 10
console.log(result + 10)        //20
console.log(result + '10')      //1010

以上,链式调用后,可输出结果,并且使用结果进行运算等操作时,会隐式转换为相应类型

不使用柯里化函数,直接写链式adder()方法:

function nomalAdder(...nums) {
    var savedArgs = nums || [];//首次调用,储存参数或者初始化储存参数的数组
    var innerFunc = (...newNums) => {//返回的链式闭包函数
        if (newNums.length > 0)//还有参数调用,继续存
        {
            savedArgs.push(...newNums)
            return innerFunc
        }
        else//参数为空时,停止链式调用
        {
            return savedArgs.reduce((a,b) => a+b)
        }
    }
    innerFunc.toString = () => innerFunc()//输出时,停止链式调用
    return innerFunc
}
调用
var result1 = nomalAdder(1,2,3,4)   //f 10
var result2 = nomalAdder(1,2)(3,4)  //f 10
var result3 = nomalAdder(1,2,3)(4)  //f 10
var result4 = nomalAdder(1)(2)(3)(4)//f 10
console.log(result1 + result2 + result3 + result4)//40

柯里化本质其实就是通过返回一个闭包函数,并将每次链式调用的函数储存起来,最后统一调用原函数的过程。举例用处:
比如需要封装一个正则判断输入数据的函数:

function check(targetString, reg) {
    return reg.test(targetString);
}

调用时:

check(/^1[34578]\d{9}$/, '14900000088');
check(/^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/, 'test@163.com');

通过柯里化,我们可以先对check函数再次进行封装,升级工厂:

var _check = createCurry(check);

var checkPhone = _check(/^1[34578]\d{9}$/);
var checkEmail = _check(/^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/);

这样使用时就会更加直观明了

checkPhone('183888888');
checkEmail('xxxxx@test.com');

以上就是个人对于柯里化的理解,因为不是太熟悉js,其中可能有理解错误。希望有路过的大神能帮忙指出来。谢谢~
另外这个思想理解起来的确有点绕,想要熟练使用,还得多练习,对于不同场景多想想如何封装代码。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容