前言
要弄清什么是函数柯里化,首先要知道函数式编程; 函数式编程是一种将函数作为参数传递和返回,并且没有副作用的一种编程方式;函数式编程也带来了很多概念,函数柯里化只是函数式编程中的一种模式,归纳的一种概念。
- 纯函数(Pure Function): 只是返回新的值,不改变系统变量;
- 柯里化(Currying): 接受多个参数的函数转换成接受单一参数的函数的操作;
- 高阶函数(Higher-order function): 一个可以接收函数作为参数,甚至返回一个函数的函数
什么是函数柯里化
函数柯里化(currying)又称部分求值。 一个currying的函数首先会接受一些参数,接受了这些参数之后,该函数并不会立即求值,而是继续返回另外一个函数,刚才传入的参数在函数形成的闭包中被保存起来。待到函数被真正需要求值的时候,之前传入的所有参数都会被一次性用于求值。
有人概括说柯里化就是将一个多元函数转换为低元函数的操作
闭包就是能够读取其他函数内部变量的函数,所以闭包可以理解成为定义在一个函数内部的函数。
案例分析讲解
这里以一个常用功能的封装做引子,我们知道判断变量类型有四种方法:
- typeof 只能判断基本数据类型,不能区分对象类型 例如typeof [] , typeof {}返回均是"object"
- constructor 可以找到这个变量是通过谁构造出来的 [].constructor ({}).constructor
- instanceof 判断谁是谁的实例,proto
- Object.prototype.toString.call()
第4钟方法能对所有的数据类型都能进行判断,即使是 null 和 undefined 。因此我们考虑将其进行封装,以便调用
封装type类型判断函数
简单封装
function isType(type,value) {
return Object.prototype.toString.call(value) === `[object ${type}]`
}
isType('Array', []) // true
isType('Array', {}) // false
isType('String', 'str') // true
上述简单封装后看起来使用还比较方便,但是有一个问题,就是如果目标类型书写有误,就会到处判断不准确,如何解决?
解决输入有误导致判断不准确问题
解决方法一:抛出异常
const typeArr = ['String','Number','Boolean', 'Array','Symbol','Null', 'Undefined','Function','Object','Arguments']
function isType(type,value) {
if (!typeArr.includes(type)) {
throw(`${type} is not in ${typeArr}`)
}
return Object.prototype.toString.call(value) === `[object ${type}]`
}
缺陷:虽然解决了使用时书写错误会引发的判断不准确问题,但问题也很明显,
- 第一个是使用时还是需要输入多个参数,使用不太友好,多种同类型判断需要多次重复输入相关目标类型;
- 第二个需要将所有正确的类型进行涵盖,不具有普遍性;
思路:我们想看能不能更细分,比方直接判断是不是isArray([]),isString('xx'),只用传入需要判断的值就行
解决方法二:柯里化
function isType(type) {
return function(value) {
return Object.prototype.toString.call(value) === `[object ${type}]`
}
}
// 定义
const isArray = isType('Array')
const isString = isString('String')
// 使用
console.log(isArray([])) // true
console.log(isArray('1234')) // false
console.log(isString('1234')) // true
console.log(isString(1234)) // false
这样我们在需要经常使用的几个的类型就可以定义出其对应的方法;
归纳与扩展
仔细看,柯里化之后的函数和未柯里化的简单的函数的关系
// 未柯里化的函数
function isType(type,value) {
return Object.prototype.toString.call(value) === `[object ${type}]`
}
// 未柯里化函数的使用
isType('Array', []) // true
// 柯里化后的函数
function isType(type) {
return function(value) {
return Object.prototype.toString.call(value) === `[object ${type}]`
}
}
// 柯里化之后的使用
const isArray = isType('Array')
console.log(isArray([]))
// 等同于
console.log(isType('Array')([]))
通过比较发现,其实柯里化后将原本一次调用需要传入两个参数('Array', [])
变成了使用时先传入一个参数('Array')
返回一个函数接收下一个参数([])
这就引出一个问题,上述函数只有两个形参,我们return一个函数,如果有多个形参呢,那就要多层嵌套了;我们希望每个参数都可以单独传入,这样多次使用的可以相对固话的参数,就可以实现再次封装了。是否可以实现一个通用的柯里化函数,实现柯里化方法?
通用柯里化函数封装
这里用一个参数比较多的示例讲解
function sum(a,b,c,d,e) {
return a + b + c + d + e
}
sum(1,2,3,4,5)
这里的sum有5个参数,我们仿照上面的isType('Array')([])希望调用时是这样酱紫的
function sum(a,b,c,d,e) {
return a + b + c + d + e
}
// 通用柯里化
function currying(fn) {
//
return function () {
//
}
}
const getSum = currying(sum)
console.log(getSum(1)(2,3)(4)(5))
// 或者
currying(sum)(1)(2,3)(4)(5)
每次传入都不一定,保证直到5个都传齐全时才最终返回需要的结果
开干
function currying(fn, arr = []) {
// (function(a,b,c,d,e) {}).length 获取传入函数的形参的个数
let fnParamsLen = fn.length
// 高阶函数
return function (...args) {
let concatArr = [...arr, ...args]
if (concatArr.length < fnParamsLen) {
// 递归 不停的产生函数
return currying(fn, concatArr)
} else {
return fn(...concatArr)
}
}
}
// 测试
// 1. 5参数求和
const getSum = currying(function (a,b,c,d,e) {
return a + b + c + d + e
})
console.log(getSum(1)(2,3)(4)(5)) // 15
// 2. 类型判断
const getType = currying(function (type,value) {
return Object.prototype.toString.call(value) === `[object ${type}]`
})
console.log(getType('Array')('')) // false
console.log(getType('Array')([])) // true
扩展·封装ajax的get和post请求
封装ajax的get和post请求,每次请求都需要传入请求类型type,因此可以考虑柯里化固定这个参数
function Ajax() {
this.xhr = new XMLHttpRequest()
}
// 核心处理器
Ajax.prototype.open = function(type,url,data,callback) {
this.onload = function() {
callback(this.xhr.responseText, this.xhr.status, this.xhr)
}
this.xhr.open(type, url, data.async);
this.xhr.send(data.paras);
}
// 在内存中推入一个get post方法,传入固定的第一个参数type,分别为get和post
'get post'.split(' ').forEach(function(type) {
Ajax.prototype[type] = currying(Ajax.prototype.open,type)
})
var xhr1 = new Ajax()
// 调用柯里化后的函数,传入余下的参数url,data,callback
xhr1.get(url,data,callback)
var xhr2 = new Ajax();
xhr2.post(url,data,callback);
总结,柯里化是函数链式编程的一种运用,里面涉及到闭包,因此其缺点也是非常明显的,总结如下
优点:1. 入口单一,易于测试和复用; 2. 易于定位bug,可以确定是哪个参数在哪个环节出了问题。
缺点:函数嵌套,占用内存,效率低,毕竟每个function都会生成一个单独的作用域,都会在调用栈中占据一块内存空间。
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!