1.原理
函数柯里化的原理是利用函数执行可以形成一个不销毁的私有作用域,把预先处理的内容都存在这个不销毁的作用域里面,并且返回一个小函数,以后要执行的就是这个小函数。
2.作用
- 参数复用 需要输入多个参数,最终只需输入一个,其余通过arguments来获取
- 提前返回 避免重复去判断某一条件是否符合,不符合则return 不再继续执行下面的操作
- 延迟执行 避免重复的去执行程序,等真正需要结果的时候再执行
分别举例说明三个作用,参数复用见第三部分函数执行时机的例子,不再赘述。
//提前返回(常用例子),用来减少if,else-if的判断次数,下次直接调用return 的函数
var addEvent = (function(){
if (window.addEventListener) {
return function(el, sType, fn, capture) {
el.addEventListener(sType, function(e) {
fn.call(el, e);
}, (capture));
};
} else if (window.attachEvent) {
return function(el, sType, fn, capture) {
el.attachEvent("on" + sType, function(e) {
fn.call(el, e);
});
};
}
})();
// 延迟执行
var curryScore=function(fn) {
var allScore = [];
return function () {
if (arguments.length === 0) {
fn.apply(null, allScore)
} else {
allScore.push(...arguments);
}
};
}
var result = 0;
var addScore = curryScore(function () {
//console.log(this)
//console.log(arguments);
for (var i = 0; i < arguments.length; i++) {
result += arguments[i];
}
})
addScore(3);
addScore(3);
console.log(result)
addScore(3);
addScore() //这里是真正开始计算的
console.log(result)
3.函数的执行时机
- 判断最后一次调用时传入的参数特征
- 隐式类型转化:valueOf和toString
- 判断原函数(被柯里化函数的参数数量)
以一道经典面试题目为例,列举这三种方法。
题目: 实现一个add方法,使计算结果能够满足如下预期:
add(1)(2)(3) = 6;
add(1, 2, 3)(4) = 10;
add(1)(2)(3)(4)(5) = 15;
// 判断最后一次调用时传入的参数特征(最后一次传入参数长度是否大于0)
function add (x,y,z) {
return x+y+z
}
function currying (fn,...arg) {
var all=[...arg]
console.log(all)
let add_temp= function (){
if (arguments.length>0) {
all.push(...arguments)
return add_temp
} else {
return fn.apply(null,all)
}
}
return add_temp
}
console.log(currying(add,1)(2,3)()) //6
console.log(currying(add,4)(2,9)()) //15
console.log(currying(add,1)(2,7)()) //10
Note:由于add.length为3,所以最多只能计算三个参数之和。
// 隐式类型转化
function add () {
// 第一次执行时,定义一个数组专门用来存储所有的参数
var all=[...arguments]
console.log(all)
// 在内部声明一个函数,利用闭包的特性保存_args并收集所有的参数值
let add_temp=function (){
all.push(...arguments)
return add_temp
}
// 利用toString隐式转换的特性,当最后执行时隐式转换,并计算最终的值返回
add_temp.toString= function(){
return all.reduce((x,y)=>{
return x+y})
}
return add_temp
}
console.log(+add(1)(2)(3))
console.log(+add(1, 2, 3)(4))
console.log(+add(1)(2)(3)(4,5)(5))
//判断原函数add的参数长度以此来递归
function add (x,y,z) {
return x+y+z
}
function currying (fn,...arg) {
var that = this
var len = fn.length //注意len是fn(即传入的add()函数)的参数长度本例中恒都为3,与调用的函数参数长度无关
var all = [...arg]
//console.log(all)
//console.log(that)
var add_temp= function () {
all.push(...arguments)
if (all.length < len) { // 如果参数个数小于最初的fn.length,则递归调用,继续收集参数
return currying.call(that, fn, ...all)
}else{
return fn.apply(this,all)
}
}
return add_temp
}
console.log(currying(add,1)(2)(3)) //6
console.log(currying(add,4)(2,9)) //15
console.log(currying(add,1)(2,7)) //10
4. 反柯里化
这是一个泛型化的过程。它使得被反柯里化的函数,可以接收更多参数。使本来只有特定对象才适用的方法,扩展到更多的对象。
function.prototype.uncurrying = function() {
var that = this;
return function() {
return Function.prototype.call.apply(that, arguments);
}
};
function sayHi () {
return "Hello " + this.value +" "+[].slice.call(arguments);
}
var sayHiuncurrying=sayHi.uncurrying();
console.log(sayHiuncurrying({value:'world'},"hahaha"));
Note:sayHiuncurrying({value:‘world’},“hahaha”)记作sayHiuncurrying(arg1,arg2),调用uncurrying()之后为sayHi.call(arg1,arg2),最后为arg1.sayHi(arg2),股输出结果为:
Hello world hahaha
5.bind方法
Function.prototype.bind 方法也是柯里化应用,原理如下:
function.prototype.bind= function(context){
// 将参数解析为数组
const args = Array.prototype.slice.call(arguments)
// 当前函数
const self = this
// 返回一个函数
return function() {
// 执行原函数,并返回结果
return self.apply(context, args)
}
}