You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
constcurry=fn=>{if(typeoffn!=='function'){thrownewError('no function provided!')}// 因为要递归,使用箭头函数会不方便returnfunctioncurriedFn(...args){// 递归出口放在前面,更加好理解if(args.length===fn.length){returnfn(...args)}// 箭头函数没有 arguments 需要显示给出参数return(...params)=>{returncurriedFn(...args.concat(params))}}}
柯里化如何改善代码
有一个日志函数:
constloggerHelper=(mode,initialMessage,errorMessage,lineNO)=>{switch(mode){case'DEBUG':
console.debug(initialMessage,`${errorMessage} at line ${lineNO}`)breakcase'ERROR':
console.error(initialMessage,`${errorMessage} at line ${lineNO}`)breakcase'WARN':
console.warn(initialMessage,`${errorMessage} at line ${lineNO}`)breakdefault:
thrownewError('Wrong mode!')}}// 习惯样调用loggerHelper('ERROR','Error at index.js','报错了',10)loggerHelper('ERROR','Error at main.js','报错了',13)
还能改善吗?
以上调用在传递很多重复参数,可以使用柯里化改善。
::: tip 柯里化帮助去除重复参数和样板代码
consterrorLog=curry(loggerHelper)('ERROR')errorLog('Error at index.js','报错了',10)errorLog('Error at index.js')('报错了',10)errorLog('Error at index.js')('报错了')(13)// 柯里化时传递所有参数,得到最后的调用结果curry(loggerHelper)('ERROR','Error at main.js','报错了',130)// 部分应用所有参数,需要再调用一次函数才能得到结果consterrorLog2=partialLeft(loggerHelper,'ERROR','Error at index.js','报错了',130)errorLog2()
constsplitSentence=str=>str.split(' ')constcount=array=>array.lengthconststr=`hello function programming`constwordCount=compose(count,splitSentence)(str)
constoddOrEven=count=>(count%2===0 ? 'even' : 'odd')constoddOrEvenWords=compose(oddOrEven,count,splitSentence)conststr=`hello function programming react`console.log(oddOrEvenWords(str))// even
constisOdd=str=>str==='odd'constisOddWords=pipe(splitSentence,pipe(count,oddOrEven),isOdd)conststr=`hello function programming react`console.log(isOddWords(str))// false
函数计算得到函数
函数式编程中把函数当成值,那么能根据函数计算得到新函数吗?当然能。
通过函数计算得到新的函数,我们便可以写更小的函数,便于测试和维护。
高阶函数、柯里化、部分应用、组合和管道等,都是由函数计算得到函数的方法。
高阶函数
js 里又不少高阶函数:map、find、some、forEach、 setTimeout 等。
高级函数是函数的更高级级的函数抽象和封装,可以代码更容易理解和复用。
React 受控组件收集表单数据。
如果不使用高阶函数 每个表单项都要写一个事件处理函数,难以阅读和难以维护。
或者这样写:
显然,高阶函数更加优雅。
部分应用
函数的默认参数
ES6 支持默认参数
默认参数使得传递部分参数即可调用函数,给使用函数带来方便。
通过闭包改善以上的问题
::: tip 温习闭包
闭包:内层函数能记住外层函数作用域的特性。
闭包可访问三个作用域的变量:
闭包在外层函数执行时候创建,访问外层函数的作用域是使用闭包的目的。
部分应用、柯里化等技术,都依赖闭包。
函数式编程语言都有闭包的特性。
:::
使用闭包对默认参数的限制有所改善,还是不够灵活:返回的函数只支持一个参数。
要是能有一个函数,将上面的
rangeNumber
函数自动计算得到一个支持传递部分参数的新函数,在使用时,支持把所有参数分成任意两部分
传递,就更加灵活了,部分应用就是这种技术。:::tip 部分应用
将函数调用分成两个阶段,第一个阶段传递一部分参数,返回一个函数,第二阶段调用新的函数,传递剩余参数。
这种函数使用方式叫部分应用,可以有效拆分参数,给函数使用带来方便。
:::
改写上面的函数:
上面的部分应用函数,第一阶段传递的参数最后调用时,是在左边的,它无法处理最终调用在右边的参数。
编写一个处理右边参数的部分应用
向右边的部分应用,和函数的默认参数顺序一致,不用改写函数。
Number.parseInt(numberStr, radix)
radix 指定 numberStr 的进制,默认 10. 使用部分应用向右 vs 向左
向右的部分应用不用改写原函数,能充分利用函数的默认参数,向左的则不能,默认情况下都是向右的才是我们希望的,向左的我们另外定义函数。
柯里化
对函数多次部分应用,得到的新函数的参数越来越少。
一次性对函数的所有参数部分应用,也要再调用一次函数才能得到结果。
可以不必再调用一次函数吗?柯里化能避免再调用一次函数。
::: tip 柯里化
将多参函数变成一系列单参函数的手段。
:::
比如:
希望编写一个函数,自动把 sum 变成单参函数。
上面柯里化一个三个参数的函数,任意数量的参数如何柯里化呢?
关键代码:
实参数量小于形参数量,就递归调用
。因为在函数内部需要使用
arguments
获取实参。::: tip 箭头函数的特性
yield
操作符号;:::
Array.from(arguments)
将类类数组转为数组。::: tip 常见的转 arguments 为数组的方法
按照性能排序:
const toArray=(...params)=>params
;[].slice.call(arguments)
;Array.from(arguments)
;:::
将上面的改成箭头函数的写法:
柯里化如何改善代码
有一个日志函数:
以上调用在传递很多重复参数,可以使用柯里化改善。
::: tip 柯里化帮助去除重复参数和样板代码
:::
再来一个例子:
改进 1:
达到目的了,但是我们不得不在 filter 的回调函数中再次调用 startsWith,希望
filter(startsWith('a'))
, 显然这个更加可读,如何办?改进 2:
把 startsWith 柯里化,
参数顺序
很重要,外层函数的参数一般是固定的。部分应用 vs 柯里化
通过部分应用和柯里化,能把多参函数变成参数更小、行为更加具体的函数,方便使用。
参数更少:调用时更加方便,只需要关注变化的参数。
行为更加具体:反映到函数名称上,让函数更加自文档化。
柯里化时更加彻底的部分应用,使用部分应用,还需要再调用一次函数。
fn.length vs arguments.length
函数有一个
length
属性,表示必需
的参数数量,arguments.length
表示实际参数数量,当有默认参数和剩余参数时,两者可能不等。我们的柯里化函数用到了
fn.length
,因此无法处理默认值。为了能使用函数的默认值,可以在提供一个参数。
柯里化性能差?
::: warning 使用频繁的函数不要柯里化
由于柯里化层层嵌套,当参数很多,嵌套会很深,频繁执行的函数,柯里化后比不柯里化的函数慢。
:::
组合
Unix 或者 Linux 命令,想要计算单词
word
在给定文本中出现的次数:一个复杂的任务,通过管道组合三个简单的命令就完成了。
::: tip unix 理念
:::
理念 1:应该写职责单一的小函数,然后组合它们来处理复杂任务。
理念 2:小函数都需要输入,然后返回数据。
形如这样,函数
fnB
的输出作为fnA
输入的函数,叫函数组合。例子,给一个字符串数字进行四舍五入:
再看一个例子:统计
hello function programming
中单词个数以上例子,都是一个参数的函数组合,而且例子太简单,多个参数还能组合吗?
多个参数的组合
通过柯里化和部分应用,可以把多参函数转为一参函数,然后组合他们。
任意函数的组合
目前
compose
只能组合两个函数,想要组合两个以上函数如何写?上面统计单词的例子,还向指导单词数是奇数还是偶数。
fns.reverse()
,反转参数,希望从最后一个函数开始调用。compose(oddOrEven, count, splitSentence)
splitSentence 最开始调用。管道
希望能像 linux 命令那样使用
|
来连接多个命令,操作结果依次传递,即希望函数从左到右执行。使用上面的例子测试:
组合 vs 管道
思路一样,数据流向不同。感觉管道更符合直觉。
管道和组件满足结合律
再添加一个返回 true 或者 false 的函数
如何调试组合函数中的错误
组合和管道都是一些函数连续执行,如何知道哪个函数执行错误呢?
因为每个函数的处理的数据都不同,根根据数据输出,很容易推断出问题。
比如,想知道
splitSentence
的处理结果。The text was updated successfully, but these errors were encountered: