一、柯里化定义
在计算机科学中,柯里化是把
接受多个参数的函数
变换成
接受一个单一参数(最初函数的第一个参数)的函数
并且返回
接受余下参数且返回结果的新函数的技术

高阶函数

高阶函数是实现柯里化的基础,高阶函数是至少满足以下两个特性之一
1、函数可以作为参数被传递
2、函数可以作为返回值输出
 

二、柯里化通用实现方式
第一种
满足原始函数的参数个数即可以执行
1、递归写法(比较绕,但是可操作性更强,可以在继续下一轮参数收集前做其他处理)
  1. // 递归写法(比较绕,但是可操作性更强,可以在继续下一轮参数收集前做其他处理)
  2. function curry(fn, ...params) {
  3. let _args = params || [] // 提前传递的部分参数
  4. let len = fn.length // 原函数的参数个数
  5. return (...rest) => {
  6. Array.prototype.push.call(_args, ...rest)
  7. console.log('_args :', _args, ', rest :', ...rest)
  8. if (_args.length >= len) { // 收集到的参数大于等于原始函数的参数数量,则执行原函数
  9. // 置空之后,柯里化后的函数可以在满足调用条件之后,继续开始新一轮的参数收集,
  10. // 否则该函数在第一次满足参数收集后,之后的调用都是返回第一次收集完参数调用的结果
  11. /**
  12. * 不置空
  13. */
  14. // return fn.apply(this, _args) // 收集到的参数满足原函数参数个数,则执行原函数
  15. /**
  16. * 置空
  17. */
  18. let _newArgs = Array.from(_args)
  19. _args = []
  20. return fn.apply(this, _newArgs)
  21. }
  22. return curry.call(this, fn, ..._args)
  23. }
  24. }

2、具名函数写法(更浅显易懂,明确的返回具名函数

  1. // 具名函数写法(更浅显易懂,明确的返回具名函数)
  2. function curry(fn, ...params) {
  3. let _args = params || []
  4. let len = fn.length
  5. return function _fn(...rest) {
  6. Array.prototype.push.call(_args, ...rest)
  7. console.log('_args :', _args, ', rest :', ...rest)
  8. if (_args.length >= len) {
  9. /**
  10. * 不置空
  11. */
  12. // return fn.apply(this, _args)
  13. /**
  14. * 置空
  15. */
  16. let _newArgs = Array.from(_args)
  17. _args = []
  18. return fn.apply(this, _newArgs)
  19. }
  20. return _fn
  21. }
  22. }

例子

  1. // 输出日志函数
  2. // 柯里化后,收集完所有参数后,才执行,只被执行一次
  3. function log(sec, min, hour) {
  4. console.log('sec, min, hour: ', sec, min, hour)
  5. }
  6. let curryLog = curry(log) // _args : []
  7.  
  8. curryLog('3s') // _args : [ '3s' ] , rest : 3s --- 未收集满原函数参数个数,即不满足 _args.length >= len 条件,递归执行 curry 函数/ 返回具名函数
  9. curryLog('8m') // _args : [ '3s', '8m' ] , rest : 8m --- 未收集满原函数参数个数,即不满足 _args.length >= len 条件,递归执行 curry 函数/ 返回具名函数
  10. curryLog('0h')
  11. // _args : [ '3s', '8m', '0h' ] , rest : 0h
  12. // sec, min, hour: 3s 8m 0h -- 收集满参数(这里参数有三个),执行原函数
上面是收集满参数个数就执行原函数,
这里有个注意点, _args = [] 是否要置为空
置空之后,可以继续开始新一轮的参数收集
否则该函数在第一次满足参数收集,调用原函数,之后的再次调用,会一直返回第一次收集满参数后调用原函数的结果
因为 _args 每次调用都被重新赋值为之前收集的参数数组,那么以后的每次调用
它的个数都是超过原有函数的个数,会一直满足调用条件,
_args 的值一直在递增收集,但是原函数所接受的我们设定的参数是有限的,
那么超过原函数所接受的参数,接受的参数值一直是 _args 的前几个, 这样原函数调用结果都是一样的

看看置空与不置空的情况

不置空,接上面代码执行下去

  1. _args = [] // 不置空
  2. curryLog('5s')
  3. // sec, min, hour: 3s 8m 0h
  4. // _args : [ '3s', '8m', '0h', '5s' ] , rest : 5s
  5.  
  6. curryLog('6h')
  7. // _args : [ '3s', '8m', '0h', '5s', '6h' ] , rest : 6h
  8. // sec, min, hour: 3s 8m 0h
  9.  
  10. curryLog('1h')
  11. // _args : [ '3s', '8m', '0h', '5s', '6h', '1h' ] , rest : 1h
  12. // sec, min, hour: 3s 8m 0h

置空,接上面代码执行下去

  1. // _args = [] // 置空(支持新开一轮收集)
  2. curryLog('5s') // _args : [ '5s' ] , rest : 5s
  3. curryLog('6h') // _args : [ '5s', '6h' ] , rest : 6h
  4. curryLog('1h')
  5. // _args : [ '5s', '6h', '1h' ] , rest : 1h
  6. // sec, min, hour: 5s 6h 1h

第二种

可以自己控制最后执行时机
1、递归写法(比较绕,但是可操作性更强,可以在继续下一轮参数收集前做其他处理)
  1. // 递归写法(比较绕,但是可操作性更强,可以在继续下了一轮参数收集前做其他处理)
  2. function curry(fn, ...params) {
  3. let _args = params || []
  4. return (...rest) => {
  5. console.log('_args :', _args, ', rest :', ...rest)
  6. if (rest.length === 0) { // 与上面的差别在于条件判断,只要传的参数为空,即执行原函数
  7. // 是否需要置空,与上面分析情况一样
  8. /**
  9. * 不置空
  10. */
  11. // return fn.apply(this, _args)
  12. /**
  13. * 置空
  14. */
  15. let _newArgs = Array.from(_args)
  16. _args = []
  17. return fn.apply(this, _newArgs)
  18. }
  19. Array.prototype.push.call(_args, ...rest) // 自己控制最后执行时机,当前语句放于 if 判断之后,减少执行
  20. return curry.call(this, fn, ..._args)
  21. }
  22. }
 
2、具名函数写法(更浅显易懂,明确的返回具名函数
  1. // 具名函数写法(更浅显易懂,明确的返回具名函数)
  2. function curry(fn, ...params) {
  3. let _args = params || []
  4. return function _fn(...rest) { // 此处使用具名函数,用于 return,这么做逻辑更清晰;就不用像上面注释的那样,递归调用 curry 函数
  5. console.log('_args :', _args, ', rest :', ...rest)
  6. if (rest.length === 0) {
  7. /**
  8. * 不置空
  9. */
  10. // return fn.apply(this, _args)
  11. /**
  12. * 置空
  13. */
  14. let _newArgs = Array.from(_args)
  15. _args = []
  16. return fn.apply(this, _newArgs)
  17. }
  18. Array.prototype.push.call(_args, ...rest) // 自己控制最后执行时机,当前语句放于 if 判断之后,减少执行
  19. return _fn
  20. }
  21. }

例子

  1. // 输出日志函数
  2. // 柯里化后,收集完所有参数后,才执行,只被执行一次
  3. function log(sec, min, hour) {
  4. console.log('sec, min, hour: ', sec, min, hour)
  5. }
  6. let curryLog = curry(log)
  7.  
  8. curryLog('3s') // _args : [] , rest : 3s
  9. curryLog('8m') // _args : [ '3s' ] , rest : 8m
  10. curryLog('0h') // _args : [ '3s', '8m' ] , rest : 0h
  11.  
  12. curryLog('5s') // _args : [ '3s', '8m', '0h' ] , rest : 5s
  13. curryLog('6h') // _args : [ '3s', '8m', '0h', '5s' ] , rest : 6h
  14. curryLog('1h') // _args : [ '3s', '8m', '0h', '5s', '6h' ] , rest : 1h
  15. curryLog()
  16. // _args : [ '3s', '8m', '0h', '5s', '6h', '1h' ] , rest :
  17. // sec, min, hour: 3s 8m 0h --- 传入参数为空,满足执行条件,只取前三个参数
上面满足条件执行后,亦存在是否让 _args = [] 的问题
 

看看置空与不置空的情况

不置空,接上面代码执行下去

  1. // _args = [] // 不置空
  2. curryLog('5s') // _args : [ '3s', '8m', '0h', '5s', '6h', '1h' ] , rest : 5s
  3. curryLog('6h') // _args : [ '3s', '8m', '0h', '5s', '6h', '1h', '5s' ] , rest : 6h
  4. curryLog('1h') // _args : [ '3s', '8m', '0h', '5s', '6h', '1h', '5s', '6h' ] , rest : 1h
  5. curryLog()
  6. // _args : [ '3s', '8m', '0h', '5s', '6h', '1h', '5s', '6h', '1h' ] , rest :
  7. // sec, min, hour: 3s 8m 0h --- 传入参数为空,满足执行条件,只取前三个参数
置空,接上面代码执行下去
  1. // _args = [] // 置空(支持新开一轮收集)
  2. curryLog('5s') // _args : [] , rest : 5s
  3. curryLog('6h') // _args : [ '5s' ] , rest : 6h
  4. curryLog('1h') // _args : [ '5s', '6h' ] , rest : 1h
  5. curryLog()
  6. // _args : [ '5s', '6h', '1h' ] , rest :
  7. // sec, min, hour: 5s 6h 1h --- 传入参数为空,满足执行条件,只取前三个参数

三、应用场景

函数柯里化有哪些用处呢?
一、可以惰性求值
二、可以提前传递部分参数
 
1、惰性求值
  1. // 计算月度电费/水费
  2. let calMonthCost = curry(function(...rest) {
  3. let costList = Array.from(rest)
  4. return costList.reduce((prev, cur) => {
  5. return prev + cur
  6. })
  7. })
  8. calMonthCost(1)
  9. calMonthCost(2)
  10. calMonthCost(3)
  11. calMonthCost(4)
  12. calMonthCost(5)
  13. // ...
  14. calMonthCost() // 结果 15
2、可以提前传递部分参数
常规写法
  1. function curry(mode) {
  2. return function(valstr) {
  3. return new RegExp(mode).test(valstr)
  4. }
  5. }
  6. let isMoblie = curry(/\d{11}/)
  7. let isEmail = curry(/^[a-zA-Z0-9_.-]+@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*\.[a-zA-Z0-9]{2,6}$/)
  8. console.log(isMoblie('13911111111')) // true
  9. console.log(isEmail('test@qq.com')) // true
柯里化,扩展,分离
  1. function validate(mode, valstr) {
  2. return new RegExp(mode).test(valstr)
  3. }
  4. let isMoblie = curry(validate, /\d{11}/)
  5. let isEmail = curry(validate, /^[a-zA-Z0-9_.-]+@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*\.[a-zA-Z0-9]{2,6}$/)
  6. console.log(isMoblie('13911111111')) // true
  7. console.log(isEmail('test@qq.com')) // true
3、bind实现(柯里化的一种)
  1. Function.prototype.bind = function(context) {
  2. let _this = this
  3. let args = [].slice.call(arguments, 1)
  4. return function() {
  5. return _this.apply(context, args.concat([].slice.call(arguments)))
  6. }
  7. }

四、反柯里化

反柯里化:扩大方法的适用范围
1、可以让任何对象拥有其他对象的方法(改变原来方法上下文)
2、增加被反柯里化方法接收的参数
例子1
  1. function uncurrying(fn) {
  2. return function() {
  3. let args = [].slice.call(arguments)
  4. let that = args.shift()
  5. fn.apply(that, args)
  6. }
  7. }
  8.  
  9. let person = {
  10. name: 'jolin',
  11. age: 18
  12. }
  13. let util = {
  14. sayPerson: function(...rest) {
  15. console.log('...rest :', ...rest)
  16. console.log('name: ', this.name, ', age: ', this.age)
  17. }
  18. }
  19.  
  20. let uncurrySayPerson = uncurrying(util.sayPerson)
  21. uncurrySayPerson(person, 'test') // person 代表 util.sayPerson 的上下文,后面的都是参数 util.sayPerson 的参数
  22. // ...rest : test
  23. // name: jolin , age: 18
  24.  
  25. // 实际上平常我们是这么写的
  26. util.sayPerson.call(person, 'test') // person 代表 util.sayPerson 的上下文,后面的都是参数 util.sayPerson 的参数
  27. // ...rest : test
  28. // name: jolin , age: 18

例子2

  1. Function.prototype.uncurrying = function() {
  2. let _this = this // 这里指 Array.prototype.push
  3. return function() {
  4. return Function.prototype.call.apply(_this, arguments)
  5. // 1、这里暂时将 Function.prototype.call 中的 call 方法叫做 changeFn
  6. // 2、那么 Function.prototype.changeFn.apply(Array.prototype.push, arguments)
  7. // 3、Array.prototype.push.changeFn(arguments)
  8. // 4、changeFn 等于 Function.prototype.call 中的 call 方法
  9. // 5、最终等价于 Array.prototype.push.call(arguments)
  10. // 6、call 方法接受的第一个参数代表上下文,进一步拆分 Array.prototype.push.call(arguments[0], ...arguments[n-1])
  11. }
  12. }
  13. let push = Array.prototype.push.uncurrying()
  14. let obj = {}
  15. push(obj, 'hh') // obj 代表 Array.prototype.push 的上下文,后面的都是参数 Array.prototype.push 的参数
  16.  
  17. // 实际上平常我们是这么写的
  18. Array.prototype.push.call(obj, 'hh') // obj 代表 Array.prototype.push 的上下文,后面的都是参数 Array.prototype.push 的参数

JavaScript中的函数柯里化与反柯里化的更多相关文章

  1. 浅析 JavaScript 中的 函数 currying 柯里化

    原文:浅析 JavaScript 中的 函数 currying 柯里化 何为Curry化/柯里化? curry化来源与数学家 Haskell Curry的名字 (编程语言 Haskell也是以他的名字 ...

  2. 浅析 JavaScript 中的 函数 uncurrying 反柯里化

    柯里化 柯里化又称部分求值,其含义是给函数分步传递参数,每次传递参数后部分应用参数,并返回一个更具体的函数接受剩下的参数,这中间可嵌套多层这样的接受部分参数函数,直至返回最后结果. 因此柯里化的过程是 ...

  3. JS 函数的柯里化与反柯里化

    ===================================== 函数的柯里化与反柯里化 ===================================== [这是一篇比较久之前的总 ...

  4. js高阶函数应用—函数柯里化和反柯里化(二)

    第上一篇文章中我们介绍了函数柯里化,顺带提到了偏函数,接下来我们继续话题,进入今天的主题-函数的反柯里化. 在上一篇文章中柯里化函数你可能需要去敲许多代码,理解很多代码逻辑,不过这一节我们讨论的反科里 ...

  5. JS的防抖,节流,柯里化和反柯里化

    今天我们来搞一搞节流,防抖,柯里化和反柯里化吧,是不是一看这词就觉得哎哟wc,有点高大上啊.事实上,我们可以在不经意间用过他们但是你却不知道他们叫什么,没关系,相信看了今天的文章你会有一些收获的 节流 ...

  6. JavaScript正则表达式详解(二)JavaScript中正则表达式函数详解

    二.JavaScript中正则表达式函数详解(exec, test, match, replace, search, split) 1.使用正则表达式的方法去匹配查找字符串 1.1. exec方法详解 ...

  7. 前端学习 第六弹: javascript中的函数与闭包

    前端学习 第六弹:  javascript中的函数与闭包 当function里嵌套function时,内部的function可以访问外部function里的变量 function foo(x) {   ...

  8. JavaScript中Eval()函数的作用

    这一周感觉没什么写的,不过在研究dwz源码的时候有一个eval()的方法不是很了解,分享出来一起学习 -->首先来个最简单的理解 eval可以将字符串生成语句执行,和SQL的exec()类似. ...

  9. 【JavaScript】Javascript中的函数声明和函数表达式

    Javascript有很多有趣的用法,在Google Code Search里能找到不少,举一个例子: <script> ~function() { alert("hello, ...

  10. 谈谈javascript 中的函数问题

    聊聊javascript中的函数 本文可作为李刚<疯狂htmlcssjavas讲义>的学习笔记 先说一个题外话 前几天在知乎上流传着一个对联  上联是雷锋推到雷峰塔 nnd 这是什么对联? ...

随机推荐

  1. 利用Process类创建多个子进程对象执行任务,主进程负责调度

    import time from multiprocessing import Process def run1(): for i in range(5): print("sunck is ...

  2. Employment Planning

    Employment Planning 有n个月,每个月有一个最小需要的工人数量\(a_i\),雇佣一个工人的费用为\(h\),开除一个工人的费用为\(f\),薪水为\(s\),询问满足这n个月正常工 ...

  3. Java网络爬虫笔记

    Java网络爬虫笔记 HttpClient来代替浏览器发起请求. select找到的是元素,也就是elements,你想要获取具体某一个属性的值,还是要用attr("")方法.标签 ...

  4. openFrameworks Download

    { https://openframeworks.cc/zh_cn//download/ } 0.10.1 是最新发布的版本. 这个版本是修改了一些BUG的小版本,与版本 0.10.1100%兼容而且 ...

  5. 深入理解Magento – 第十章、十一章(英文原版地址,仅供参考)

    深入理解Magento – 第十章 – Magento系统覆盖和升级 http://alanstorm.com/magento_upgrade_rewrite_override 深入理解MAGENTO ...

  6. SCP-bzoj-1068

    项目编号:bzoj-1068 项目等级:Safe 项目描述: 戳这里 特殊收容措施: 区间DP.f[l][r][s]表示l到r的子串能最小被压成的长度,其中s∈[0,1]表示该串压缩后串中是否能含有M ...

  7. springboot设置静态资源缓存一年

    由于本人所在项目组,所用的项目是前后端分离的,前端是React 的SPA,每次打包都会新版本的静态文件. 然而,在有些时候,这些静态资源是不变的,故可以将资源缓存至用户本地,提升性能. 写法如下,需要 ...

  8. 安装beanstalkd - centos

    安装: wget https://github.com/kr/beanstalkd/archive/v1.9.tar.gz beanstalkd_v1. beanstalkd_v1..tar.gz . ...

  9. 51nod 1437 迈克步——单调栈

    有n只熊.他们站成一排队伍,从左到右依次1到n编号.第i只熊的高度是ai. 一组熊指的队伍中连续的一个子段.组的大小就是熊的数目.而组的力量就是这一组熊中最小的高度. 迈克想知道对于所有的组大小为x( ...

  10. atlcomcli.h(1756): error C2338: CVarTypeInfo< char > cannot be compiled with /J or _CHAR_UNSIGNED fl

    我拿到一个VS的工程,用VS2010 编译 时提示: atlcomcli.h(1756): error C2338: CVarTypeInfo< char > cannot be comp ...