柯里化的两种实现方式(定参和不定参)
1. 函数柯里化简介
**函数柯里化是指把接收多个参数的函数转换为接受单一参数的函数,并返回接收剩下参数的新函数的技术。**通俗点说,就是将多元函数转化为多个单元函数的连续定义(这里的元代指参数)。
也就说函数柯里化可以把f(a,b,c)这样的多参的函数转换成f(a)(b)©这样的函数,经过转换后的函数每次依次接收单一参数,并且返回最终结果。
柯里化其实本质上是一种编程思想,函数执行产生一个闭包,可以把一些信息存储下来,目的是供下级上下文调用。所以函数柯里化的核心其实是预先存储+调用。
举个例子:
// 一个一次性接受三个参数的普通函数
function add(a, b, c) {return a + b + c;
}
add(1, 2, 3); // 6function curry(fn) {// 函数柯里化会将add普通函数转换成一个新的函数fn,这个fn可以接受单个参数,并返回函数fn1// 下面会介绍具体实现
}// 经过函数柯里化转换之后的函数
const newAdd = curry(sum);
// 返回一个可以接收第二个参数的函数
const add1 = newAdd(1);
// 返回一个可以接收第三个参数的函数
const add2 = add1(2);
// 传入第三个参数,返回最终结果
// 本质是借用了js闭包的特性,内部保存了之前的值1和2,从而得到6
const result = add2(3) // 6
2. 应用场景
函数柯里化可以让我们在使用函数时对参数进行自由处理,降低了通用性,提升了适用性。
假设我们在日常使用ajax发起请求时。我们有一个通用的请求的getAjax
函数来发起请求。
function ajax(method, url, query) {const xhr = new XMLHttpRequest();xhr.open(method, `${url}?${query}`);xhr.send();xhr.onreadystatechange = function() {if (xhr.readystate === 4) {// xxx}}
}
// 这几个请求之间存在相同的参数
ajax('POST', 'www.test.com', 'name=zs')
ajax('POST','www.test2.com', 'name=ls')
ajax('POST','www.test3.com', 'name=ww')
如果它们有共同的请求方式,仍旧需要进行三次相同的传参,可以使用函数柯里化将其通用参数保存在闭包里,实现复用。它们和原函数相比,从功能上来说,降低了通用型性,但提升了适用性,因为它进行了参数复用。
const newAjax = curry(ajax);
const getPostAjax = newAjax('POST');
// 可以直接复用之前的残暑
getPostAjax('www.test.com', 'name=zs')
getPostAjax('www.test2.com', 'name=ls')
getPostAjax('www.test3.com', 'name=ww')
再看一个例子:
假如我们有这样这样一段数据,obj
是一个对象数组,里面每一项都是一个对象
const obj = [{name: 'zs',age: 20,},{name: 'lisi',age: 23,}
]
如果此时需要获取对象里面每一项数据的`name属性值,一般我们会这样去做:
const nameList = obj.map(item => item['name']);
假如用函数柯里化去实现:
// 封装一个获取props参数的通用函数
function props = curry(function(key, obj) {return obj[key];
})
// 通过通用函数获取属性
let nameList = obj.map(props('name'));
应用函数柯里化之后,该props函数以后可以被多次使用。
而且在考虑代码复杂度的时候,是可以将该props函数去掉的,该函数内可以理解为只有一句let nameList = obj.map(props('name'));
。这样看起来代码会更加精简,并且可读性看起来也变得更高。
3. 具体实现
函数柯里化的具体实现原理其实就是保存之前传入的参数,返回接收剩余函数的函数,并返回最终结果。函数柯里化大致可以分为两种:定参函数柯里化和不定参的函数柯里化。
以sum求和为例说明。
- 定参函数柯里化
// 普通的求和函数
function sum(a,b,c) {return a+b+c
}
sum(1, 2, 3) // 6// 经过柯里化转换后的函数
let newSum = curry(sum);
newSum(1)(2)(3) //6// 柯里化的实现
function curry(fn, ...args) {return function() {// 利用js闭包的特性获取上下文函数的传参,做参数拼接args = [...args, ...arguments]// 如果当前传参的数量不够,就返回能够接收剩余参数的函数// fn.length获取的是fn函数中的形参数量if (args.length < fn.length) {// 把当前参数作为函数形参进行传递return curry.call(this, fn, ...args);} else {// 函数参数接收完成之后,执行函数return fn.apply(this, args);}}
}
不定参数函数的柯里化
不定参数函数的柯里化也是利用了js闭包+函数隐形转换实现的。
首先内部定义一个函数curry,用curry去接收函数递归调用传递的参数和之前的参数做拼接并保存在函数内部。
判断函数结束这里其实是用到了函数的隐式转换,因为每次执行返回值是一个函数,这个时候toString方法会自动被调用。
// 不定参 function sum(...params) {// 使用闭包特性保存args变量let args = [];let curry = function() {args = [...args, ...arguments];// 返回curry,供后续调用return curry;}// return curry其实就是获取curry函数源码// 获取curry函数源码会自动调用toString方法,所以在这里我们重写toString方法做求和curry.toString = function() {const res = args.reduce((pre, cur) => {return pre + cur}, 0);// toSting返回值必须是string类型,否则会报错,因此这里做了一些字符串转换return res + '';}// 利用curry函数拼接参数// 这里需要使用rest运算符对参数再做一遍展开,因为在curry函数中rest运算符只是做了类数组对象到数组对象的转换,不会对实际数组做展开return curry(...params); }sum(1)(2)(3) // 6
我们对上面部分代码做具体分析,首先分析下面代码中的return curry
和return curry()
内部到底做了什么事情?
let curry = function() {args = [...args, ...arguments];// 返回curry,供后续调用return curry;}
return curry(...params);
当
return curry()
的时候:会去执行curry
函数体,然后抛出返回值当
return curry
时:这个时候不会去执行curry
函数体,而是试图得到curry
函数体的源码。curry
函数名在系统内部其实是指向函数的指针,它在是传递了函数体所在的内部地址位置,在需要使用时根据这个指针到内存中去查找。return curry
的curry
得到的就是函数体的源码,而要得到源码,就会自动调用toString方法。在Function需要转换为字符串时,通常会自动调用函数的toString方法,toString方法返回一个表示当前函数源代码的字符串。
函数柯里化其实利用闭包形成了一个保存在内存中的变量args,并且把后续接收到的参数都拼接在args变量中,等到后续使用,并返回一个函数接收新的参数。因此,函数柯里化 = 闭包 + 递归。
4. 特点
参数复用
在上面提到了
ajax
函数柯里化的例子中,它在内部对POST
参数进行了缓存,有效进行了参数复用,减少函数传参。提前返回
在普通函数中,需要等到所有参数都传递之后才可以执行得到一个返回结果。在函数柯里化中,每次传参执行都可以得到一个结果,这里的结果是个函数,存在着上级闭包的引用
延迟执行
在有些参数不是那么容易获取时,比如某个函数的函数是一个异步结果时,那此时就可以体现函数柯里化的优势了。如果函数参数依赖于另一个异步任务,那么就可以延迟执行,直到获取到所有异步执行的结果之后再去执行。
5. 函数柯里化与偏函数
- 偏函数
函数柯里化的一个独特应用场景就是实现偏函数。偏函数是指固定一个参数的一些函数,然后返回另一个更小元(参数)的函数,这个更小元的函数用于接收剩余参数并返回结果。
举个
柯里化的两种实现方式(定参和不定参)相关推荐
- JS实现curry(柯里化)的四种简单方式
// 方法1 function curry (fn, args) {const length = fn.lengthconst args = args || []return function () ...
- 深入详解python高级特性——函数柯里化(Currying)与反柯里化
前言:本章的内容本来很简单,但是涉及到的理论部分相对较多,想要彻底弄懂前因后果需要具备以下几个知识点, (1)python的高阶函数 (2)python的装饰器本质 (3)Python的functoo ...
- 了解js基础知识中的作用域和闭包以及闭包的一些应用场景,浅析函数柯里化
js基础知识中的作用域和闭包 一.作用域 1.作用域.自由变量简介 (1)作用域定义 (2)作用域实例演示 (3)自由变量定义 (4)自由变量实例演示 2.作用域链简介 (1)作用域链定义 (2)作用 ...
- JavaScript 专题之函数柯里化
JavaScript 专题系列第十三篇,讲解函数柯里化以及如何实现一个 curry 函数 定义 维基百科中对柯里化 (Currying) 的定义为: In mathematics and comput ...
- 从一道面试题认识函数柯里化
最近在整理面试资源的时候,发现一道有意思的题目,所以就记录下来. 题目 如何实现 multi(2)(3)(4)=24? 首先来分析下这道题,实现一个 multi 函数并依次传入参数执行,得到最终的结果 ...
- 0205事件处理_受控_柯里化-组件-React
文章目录 1 事件处理 1.1 概述 1.2 示例 2 非受控组件和受控组件 2.1 概述 2.2 登录示例-非受控 2.3 登录示例-受控 3 高阶函数 4 函数柯里化 5 React函数的非柯里化 ...
- 一文讲懂什么是函数柯里化,柯里化的目的及其代码实现
柯里化(Currying) 柯里化(Currying)[1]是一种关于函数的高阶技术.它不仅被用于 JavaScript,还被用于其他编程语言. 柯里化是一种函数的转换,它是指将一个函数从可调用的 f ...
- 柯里化函数(Currying),什么是柯里化,为什么要进行柯里化,高级柯里化函数的实现
柯里化(Currying) 柯里化(Currying)是一种关于函数的高阶技术.它不仅被用于 JavaScript,还被用于其他编程语言. 柯里化是一种函数的转换,它是指将一个函数从可调用的 f(a, ...
- 深入理解函数式编程之柯里化
目录 柯里化定义 柯里化原因 柯里化前奏--需要固定数量参数 实现柯里化--期待固定数量参数 应用柯里化 流程剖析 柯里化定义 在数学和计算机科学中,柯里化是一种将使用多个参数的一个函数转化成一系列使 ...
最新文章
- ZTE-中兴捧月-北京线下测试赛--B题
- 初步理解TCP/IP网络
- iOS点滴- ViewController详解
- js小案例:定时轮播图
- JavaScript id_好程序员web前端分享Javascript中函数作为对象
- vue项目如何放到服务器上,Vue项目怎么上传到云服务器
- 【算法题1】上台阶问题
- 7.2 DOM方法(以动态方式实时创建标记,实质在改变DOM节点树)
- codeforces1012 B. Chemical table(并查集+思维)
- ipv6寻址_什么是IPV4寻址?
- WEB运用程序如何实现高效可维护?
- php怎么选择路径,利用php+mcDropdown实现文件路径可在下拉框选择
- Git合并两个不同的仓库
- 安装DCU组件出错的解决方法
- Eclipse怎样连接并打开oracle等数据库?
- Nginx (Install)
- CUDA memory
- 三角色:程序员、技术主管与架构师
- [转帖]知乎卢克文 中国的石油战略
- python查询12306余票_python命令行查询12306火车票
热门文章
- python给excel排序_Python实现自定义顺序、排列写入数据到Excel的方法
- 服务器被黑客攻击了,如何去处理?
- 【python实战】20行代码实现信息自动发送
- 苹果最新发布的系统12.4版本在XR机型有严重bug,升级后无法支持电信卡,移动正常信号弱,本人升级后又刷回到12.3.1了。
- 详解概率图模型——概述
- 如何在r9000k 2021H上安装ubuntu和nvidia驱动
- 怎么和steam好友玩rust_rust怎么加好友指令 | 手游网游页游攻略大全
- Flutter基础(九)资源和图片
- python 利用Unicode编码获得十二星座符号
- 书论61 杨慎《墨池琐录》