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

高阶函数

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

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

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

// 具名函数写法(更浅显易懂,明确的返回具名函数)
function curry(fn, ...params) {
let _args = params || []
let len = fn.length
return function _fn(...rest) {
Array.prototype.push.call(_args, ...rest)
console.log('_args :', _args, ', rest :', ...rest)
if (_args.length >= len) {
/**
* 不置空
*/
// return fn.apply(this, _args)
/**
* 置空
*/
let _newArgs = Array.from(_args)
_args = []
return fn.apply(this, _newArgs)
}
return _fn
}
}

例子

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

看看置空与不置空的情况

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

_args = [] // 不置空
curryLog('5s')
// sec, min, hour: 3s 8m 0h
// _args : [ '3s', '8m', '0h', '5s' ] , rest : 5s curryLog('6h')
// _args : [ '3s', '8m', '0h', '5s', '6h' ] , rest : 6h
// sec, min, hour: 3s 8m 0h curryLog('1h')
// _args : [ '3s', '8m', '0h', '5s', '6h', '1h' ] , rest : 1h
// sec, min, hour: 3s 8m 0h

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

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

第二种

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

例子

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

看看置空与不置空的情况

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

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

三、应用场景

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

四、反柯里化

反柯里化:扩大方法的适用范围
1、可以让任何对象拥有其他对象的方法(改变原来方法上下文)
2、增加被反柯里化方法接收的参数
例子1
function uncurrying(fn) {
return function() {
let args = [].slice.call(arguments)
let that = args.shift()
fn.apply(that, args)
}
} let person = {
name: 'jolin',
age: 18
}
let util = {
sayPerson: function(...rest) {
console.log('...rest :', ...rest)
console.log('name: ', this.name, ', age: ', this.age)
}
} let uncurrySayPerson = uncurrying(util.sayPerson)
uncurrySayPerson(person, 'test') // person 代表 util.sayPerson 的上下文,后面的都是参数 util.sayPerson 的参数
// ...rest : test
// name: jolin , age: 18 // 实际上平常我们是这么写的
util.sayPerson.call(person, 'test') // person 代表 util.sayPerson 的上下文,后面的都是参数 util.sayPerson 的参数
// ...rest : test
// name: jolin , age: 18

例子2

Function.prototype.uncurrying = function() {
let _this = this // 这里指 Array.prototype.push
return function() {
return Function.prototype.call.apply(_this, arguments)
// 1、这里暂时将 Function.prototype.call 中的 call 方法叫做 changeFn
// 2、那么 Function.prototype.changeFn.apply(Array.prototype.push, arguments)
// 3、Array.prototype.push.changeFn(arguments)
// 4、changeFn 等于 Function.prototype.call 中的 call 方法
// 5、最终等价于 Array.prototype.push.call(arguments)
// 6、call 方法接受的第一个参数代表上下文,进一步拆分 Array.prototype.push.call(arguments[0], ...arguments[n-1])
}
}
let push = Array.prototype.push.uncurrying()
let obj = {}
push(obj, 'hh') // obj 代表 Array.prototype.push 的上下文,后面的都是参数 Array.prototype.push 的参数 // 实际上平常我们是这么写的
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. loj2064[HAOI2016]找相同字符

    题意:给你两个字符串,问其中各取一个子串,有多少对相同?n<=20W. 标程: #include<bits/stdc++.h> using namespace std; typede ...

  2. Spring 整合 Redis(转)

    转自http://blog.csdn.net/java2000_wl/article/details/8543203 pom构建: <modelVersion>4.0.0</mode ...

  3. Express post请求无法解析参数的原因

    router.post('/', function(req, res) { console.log(req.body); console.log(req.body.name); console.log ...

  4. java——IO(普通文件,二进制文件,压缩文件 )

    二进制文件 压缩包

  5. 构造——cf1213E

    分情况讨论,构造很简单 #include<bits/stdc++.h> using namespace std; #define N 200005 ],t[]; int n,s1,s2,t ...

  6. MFC弹出选择文件和选择文件夹代码

    选择文件夹 TCHAR szSelectedDir[]; BROWSEINFO bi; ITEMIDLIST *il; bi.hwndOwner = m_hWnd; bi.pidlRoot = NUL ...

  7. (转)阿里RocketMQ Quick Start

    转:http://blog.csdn.net/a19881029/article/details/34446629 RocketMQ单机支持1万以上的持久化队列,前提是足够的内存.硬盘空间,过期数据数 ...

  8. npm install 超时 国内 切换源; npm ERR! code ELIFECYCLE;

    install 超时 查看npm源地址 npm config get registry #http://registry.npmjs.org 为国外镜像地址 设置阿里云镜像 npm config se ...

  9. jmter 二次开发 IDEA 项目5.1

    jmter 二次开发 IDEA 项目5.1 IDEA 编译 Jmeter 5.0(二次开发) 1. Java环境配置 1.1     步骤1 1.2     步骤2 1.3     步骤3 1.4   ...

  10. Java【并发】面试题

    精尽 Java[并发]面试题 以下面试题,基于网络整理,和自己编辑.具体参考的文章,会在文末给出所有的链接. 如果胖友有自己的疑问,欢迎在星球提问,我们一起整理吊吊的 Java[并发]面试题的大保健. ...