>[success] # 函数柯里化 1. **柯里化**也是属于**函数式编**程里面一个**概念** 2. **js 设计模式与开发实践书中的解释**:currying又称部分求值。一个currying的函数首先会接受一些参数,接受了这些参数之后,该函数并不会立即求值,而是继续返回另外一个函数, 刚才传入的参数在函数形成的闭包中被保存起来。待到函数被真正需要求值的时候, 之前传入的所有参数都会被一次性用于求值。 3. **js高级程序语言设计**与函数绑定紧密相关的主题是函数柯里化,它用于创建已经设置好了一个或多个参数的函数。 函数柯里化的基本方法和函数绑定是一样的:使用一个闭包返回一个函数。两者的区别在于,当函数被调用时,返回的函数还需要设置一些传入的参数 * 总结:当一个函数有多个参数的时候先传递一部分参数调用它(**这部分参数以后永远不变**),然后返回一个新的函数接收剩余的参数,返回结果 **柯理化函数思想**:预先存储(预处理),利用闭包的“保存机制”,我们把一些值事先存储起来,供其下级上下文中后期用 * **举个例子**柯里化是一种函数的转换,将一个函数从可调用的**f(a, b, c)** 转换为可调用的 **f(a)(b)(c)**,柯里化不会调用函数。它只是对函数进行转换。 ~~~ // 基于console.log或者alert输出一个函数,会默认先把函数转化为字符串「Symbol.toPrimitive -> valueOf -> toString」 const curring = function curring() { let params = []; // 利用闭包 const add = (...args) => { params = params.concat(args); // 将每一次调用参数都存储在闭包外部作用域函数的参数中 return add; }; add[Symbol.toPrimitive] = () => params.reduce((result, item) => result + item); return add; }; let add = curring(); let res = add(1)(2)(3); console.log(res); //->6 add = curring(); res = add(1, 2, 3)(4); console.log(res); //->10 add = curring(); res = add(1)(2)(3)(4)(5); console.log(res); //->15 ~~~ >[info] ## 柯里化优势 1. **函数的职责单一**,实际编程中望一个函数处理的问题尽可能的单一,而不是将一大堆的处理过程交给一个函数来处理 2. **函数的参数服用**,某些情况下可以复用参数逻辑 >[danger] ##### 参数复用案例 ~~~ function checkAag(age) { let min = 18 return age >= min } // 上面定义的min 参数让整个函数变得局限只能针对18来做来做判断 // 现在思路升级定义两个参数,让整个方法更加灵活 function checkAag(min, age) { return age >= min } // 都是18的基准值被反复传参 checkAge(18, 24) checkAge(18, 20) checkAge(20, 30) // ---------------------------柯里化解决--------------------------- // 柯里化是闭包的应用场景之一,他利用了闭包延长作用域的这个特性 // 将 把接受多个参数的函数变换成接受一个单一参数的函数,并且返回接受余下的参数且返回结果的新函数 // 以上面的案例来分析 我们会先传min 这参数,他会返回一个新的函数,这个新的函数会接受age参数返回我们想要的结果 const { log } = console function checkAge(min) { return function (age) { return age >= min } } // 解决了刚才同一个基准值反复 传参的问题 let checkAge18 = checkAge(18) let checkAge20 = checkAge(20) log(checkAge20(19)) // false log(checkAge18(16)) // false // ES6 写法 let checkAge = min => (age => age >= min) ~~~ >[info] ## 柯里化推到 >[danger] ##### 拓展思维逐步柯里化(一) ~~~ // 计算每天花销的钱(最传统的写法) let monthlyCost = 0 var cost = function (money) { monthlyCost += money } cost(100) cost(200) // 300 console.log(monthlyCost) ~~~ ~~~ 1.现在虽然用了闭包,比刚才的效果更好,并且已经形成了一个函数式的编程,准确的说已经形成一个 纯函数,因为此时已经不存在副作用 2.按照柯里化的概念来说,我们可以控制真正想要结果的时机,但是现在明显不可以,每次执行都会 得到一个结果,当前需求实际只想要七天的总和而不是中间步骤每一次结果 ~~~ ~~~ // 利用闭包 把 monthlyCost 作用域限制在函数内 var cost = (function () { let monthlyCost = 0 return function () { monthlyCost += [].shift.apply(arguments) return monthlyCost } })() cost(100) console.log(cost(200)) // 300 ~~~ >[danger] ##### 拓展思维逐步柯里化(二) ~~~ 1.上面两个虽然都满足就算每个礼拜的价格花销,要做的只是不停的调用 七次,如果让计算平均值,计算一个月呢? 2.在看一下函数柯里化的概念,先传递一部分参数,但是不会返回结果而是一个函数, 当想要的剩余参数全部都接受完了,此时才会返回想要的结果,下面案例是有点特殊,是 不传参数时候才会返回结果,形成的整理思路还是一样,只有想要执行结果的时候才会触发 ~~~ * 做到第一步我们只关心结果不关心过程值 ~~~ var cost = (function () { let args = [] return function () { if(arguments.length === 0){ return args.reduce((accumulator, currentValue) => accumulator+currentValue) }else{ [].push.apply(args,arguments) } } })() cost(100) console.log(cost(200)) console.log(cost()) // 300 ~~~ >[danger] ##### 最终柯里化写法 ~~~ 1.这种最终写法完成符合柯里化函数的定义 ~~~ ~~~ var currying = function (fn) { let args = [] return function () { if(arguments.length === 0){ return fn.apply(this,args) // 你想给一个方法传入多个参数用apply 小技巧当然es6用展开操运算'...' }else{ // 如果不是想执行计算的时候就返回代码本身 [].push.apply(args,arguments) return arguments.callee } } } // 通过计算总和的函数,如果不需要总和就写对应条件的函数即可 var cost = function () { return [].reduce.call(arguments,(accumulator, currentValue) => accumulator+currentValue) } var cost = currying(cost) // 因为返回的是本身所以可以这么写 //cost(100)(200) // 也可以下面这么写一直调用 cost(100) cost(200) console.log(cost()) // 300 ~~~ >[info] ## lodash 中的柯里化函数 ~~~ 1.lodash 利用的就是函数式编程的思想,其中提供的'curry'方法可以将函数转成柯里化函数,关于lodash 这个方法使用的讲解: 功能:创建一个函数,该函数接收一个或多个 func 的参数,如果 func 所需要的参数都被提 供则执行 func 并返回执行的结果。否则继续返回该函数并等待接收剩余的参数。 参数:需要柯里化的函数 返回值:柯里化后的函数 ~~~ >[danger] ##### 使用 一 ~~~ // 引入lodash const _ = require('lodash') // 创建一个函数 function getSum(a, b, c) { return a + b + c } // 使用curry 将getSum 转换成柯里化函数 let curried = _.curry(getSum) // 把接受多个参数的函数变换成接受一个单一参数的函数,并且返回接受余下的参数且返回结果的新函数 curried(1, 2, 3) // 直接执行 curried(1)(2)(3) // (1) 和 (2) 都没有直接返回结果 只有形成个数和实参一直时执行符合柯里化 // 利用柯里化将函数转成了一元函数,原本是要传三个参数,在(1,2)返回的新的函数只要传一个参数 curried(1, 2)(3) ~~~ >[danger] ##### 使用二 ~~~ 1.现在要对一个每一项是字符串组成的数组进行过滤找到,不包含空字符串的字符项 ~~~ ~~~ const _ = require('lodash') // 先柯里化 查找规则的函数 const match = _.curry(function (reg, str) { return str.match(reg) }) // 使用柯里化定义规则 const haveSpace = match(/\s+/g) // 使用柯里化创建一个数组过滤方法 const filter = _.curry(function (func, array) { return array.filter(func) }) // 使用柯里化并将比价规则的柯里化函数作为回调传入 const findSpace = filter(haveSpace) // ------------执行------------- console.log(filter(haveSpace, ['John Connor', 'John_Donne'])) console.log(findSpace(['John Connor', 'John_Donne'])) ~~~ >[danger] ##### 实现一个curry 函数 ~~~ 1.抛开lodash这种现成封装好的函数库,自己如何实现一个柯里化转换函数,主要的思路就是 要知道形参和实参是否在长度上已经相等,不相等就不会返回结果,相等则会返回对应结果内容 ~~~ [关于函数形参长度](https://www.kancloud.cn/cyyspring/more/1441815) ~~~ // 将函数柯里化 function curry(func) { return function curriedFn(...args) { // 如果实参小于形参那么就是返回回调函数 if (args.length < func.length) { // 返回函数 return function () { // 将之前函数的参数拼接回来 return curriedFn(...args.concat(Array.from(arguments))) } } // 如果相等返回实际结果 return func(...args) } } function getSum(a, b, c) { return a + b + c } const curried = curry(getSum) console.log(curried(1, 2, 3)) //6 console.log(curried(1)(2, 3)) //6 console.log(curried(1, 2)(3)) // 6 ~~~ >[info] ## 对柯里化的总结 ~~~ 1.柯里化可以让我们给一个函数传递较少的参数得到一个已经记住了某些固定参数的新函数 2.这是一种对函数参数的'缓存' 本质上就是利用闭包 3.让函数变的更灵活,让函数的粒度更小 4.可以把多元函数转换成一元函数 ~~~