美文网首页
柯里化理解笔记

柯里化理解笔记

作者: 叫我逗Bee | 来源:发表于2019-02-13 23:57 被阅读19次

前言:

最近在看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,其中可能有理解错误。希望有路过的大神能帮忙指出来。谢谢~
另外这个思想理解起来的确有点绕,想要熟练使用,还得多练习,对于不同场景多想想如何封装代码。

相关文章

  • 柯里化理解笔记

    前言: 最近在看Taro文档,里面提到了js函数的柯里化,完全不懂。搜了写资料后,大概理解了点意思,记录一下心得参...

  • 简单理解JavaScript中的柯里化和反柯里化

    简单理解JavaScript中的柯里化和反柯里化 前言 本文旨在让大家简单理解柯里化和反柯里化,这里不做深入探究,...

  • 简单理解JavaScript中的柯里化和反柯里化

    简单理解JavaScript中的柯里化和反柯里化 前言 本文旨在让大家简单理解柯里化和反柯里化,这里不做深入探究,...

  • 理解柯里化

    JavaScript函数柯里化,我们直接来看一段普通的代码: 上面这个函数很简答, 就是对传入的a、b、c参数进行...

  • 函数柯里化(应用)

    简介 本章着重讲解一下柯里化的应用,以利于对柯里化的理解和深入。柯里化只是一门技术,其实就是化简了代码。 事件监听...

  • 柯里化函数实现

    柯里化函数实现 柯里化函数的实现实质上是一个收集参数的过程, 也许柯里化的内涵及应用场景理解起来比较困难, 但是实...

  • 前端进击的巨人(五):学会函数柯里化(curry)

    柯里化(Curring, 以逻辑学家Haskell Curry命名) 写在开头 柯里化理解的基础来源于我们前几篇文...

  • 手写简单.bind()实现

    手写一个实现柯里化的.bind() 柯里化:《函数柯里化小结》柯里化:前端开发者进阶之函数柯里化Currying ...

  • 理解Javascript的柯里化

    前言 本文1454字,阅读大约需要4分钟。 总括: 本文以初学者的角度来阐述Javascript中柯里化的概念以...

  • FP入门的一个重要知识点:柯里化

    FP入门概念必须掌握的是“纯函数”,“柯里化”,“函数组合”。就算只是作为一个FP新手,理解柯里化也是基本的要求。...

网友评论

      本文标题:柯里化理解笔记

      本文链接:https://www.haomeiwen.com/subject/kgujeqtx.html