第一次看到柯里化这个词的时候,还是在看一篇算法相关的博客提到把函数柯里化,那时一看这个词就感觉很高端,实际上当你了解了后才发现其实就是高阶函数的一个特殊用法。
果然是不管作用怎么样都要有个高端的名字才有用。
首先看看柯里化到底是什么?
维基百科上说道:柯里化,英语:Currying(果然是满满的英译中的既视感),是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。
看这个解释有一点抽象,我们就拿被做了无数次示例的add函数,来做一个简单的实现。
function add(x
, y
) {
return x
+ y
}
function curryingAdd(x
) {
return function (y
) {
return x
+ y
}
}
add(1, 2)
curryingAdd(1)(2)
实际上就是把add函数的x,y两个参数变成了先用一个函数接收x然后返回一个函数去处理y参数。现在思路应该就比较清晰了,就是只传递给函数一部分参数来调用它,让它返回一个函数去处理剩下的参数。
但是问题来了费这么大劲封装一层,到底有什么用处呢?没有好处想让我们程序员多干事情是不可能滴,这辈子都不可能.
来列一列Currying有哪些好处呢?
1. 参数复用
function check(reg
, txt
) {
return reg
.test(txt
)
}
check(/\d+/g, 'test')
check(/[a-z]+/g, 'test')
function curryingCheck(reg
) {
return function(txt
) {
return reg
.test(txt
)
}
}
var hasNumber
= curryingCheck(/\d+/g)
var hasLetter
= curryingCheck(/[a-z]+/g)
hasNumber('test1')
hasNumber('testtest')
hasLetter('21212')
上面的示例是一个正则的校验,正常来说直接调用check函数就可以了,但是如果我有很多地方都要校验是否有数字,其实就是需要将第一个参数reg进行复用,这样别的地方就能够直接调用hasNumber,hasLetter等函数,让参数能够复用,调用起来也更方便。
2. 提前确认
var on = function(element
, event
, handler
) {
if (document
.addEventListener
) {
if (element
&& event
&& handler
) {
element
.addEventListener(event
, handler
, false);
}
} else {
if (element
&& event
&& handler
) {
element
.attachEvent('on' + event
, handler
);
}
}
}
var on
= (function() {
if (document
.addEventListener
) {
return function(element
, event
, handler
) {
if (element
&& event
&& handler
) {
element
.addEventListener(event
, handler
, false);
}
};
} else {
return function(element
, event
, handler
) {
if (element
&& event
&& handler
) {
element
.attachEvent('on' + event
, handler
);
}
};
}
})();
var on = function(isSupport
, element
, event
, handler
) {
isSupport
= isSupport
|| document
.addEventListener
;
if (isSupport
) {
return element
.addEventListener(event
, handler
, false);
} else {
return element
.attachEvent('on' + event
, handler
);
}
}
我们在做项目的过程中,封装一些dom操作可以说再常见不过,上面第一种写法也是比较常见,但是我们看看第二种写法,它相对一第一种写法就是自执行然后返回一个新的函数,这样其实就是提前确定了会走哪一个方法,避免每次都进行判断。
3. 延迟运行
Function
.prototype
.bind = function (context
) {
var _this
= this
var args
= Array
.prototype
.slice
.call(arguments
, 1)
return function() {
return _this
.apply(context
, args
)
}
}
像我们js中经常使用的bind,实现的机制就是Currying.
说了这几点好处之后,发现还有个问题,难道每次使用Currying都要对底层函数去做修改,
有没有什么通用的封装方法?
var currying = function(fn
) {
var args
= Array
.prototype
.slice
.call(arguments
, 1)
return function() {
var newArgs
= args
.concat(Array
.prototype
.slice
.call(arguments
))
return fn
.apply(this, newArgs
)
}
}
这边首先是初步封装,通过闭包把初步参数给保存下来,然后通过获取剩下的arguments进行拼接,最后执行需要currying的函数。
但是好像还有些什么缺陷,这样返回的话其实只能多扩展一个参数,currying(a)(b)©这样的话,貌似就不支持了(不支持多参数调用),一般这种情况都会想到使用递归再进行封装一层。
function progressCurrying(fn
, args
) {
var _this
= this
var len
= fn
.length
;
var args
= args
|| [];
return function() {
var _args
= Array
.prototype
.slice
.call(arguments
);
Array
.prototype
.push
.apply(args
, _args
);
if (_args
.length
< len
) {
return progressCurrying
.call(_this
, fn
, _args
);
}
return fn
.apply(this, _args
);
}
}
这边其实是在初步的基础上,加上了递归的调用,只要参数个数小于最初的fn.length,就会继续执行递归。
好处说完了,通用方法也有了,让我们来关注下curry的性能
curry的一些性能问题你只要知道下面四点就差不多了:
存取arguments对象通常要比存取命名参数要慢一点一些老版本的浏览器在arguments.length的实现上是相当慢的使用fn.apply( … ) 和 fn.call( … )通常比直接调用fn( … ) 稍微慢点创建大量嵌套作用域和闭包函数会带来花销,无论是在内存还是速度上
其实在大部分应用中,主要的性能瓶颈是在操作DOM节点上,这js的性能损耗基本是可以忽略不计的,所以curry是可以直接放心的使用。
最后再扩展一道经典面试题
add(1)(2)(3) = 6;
add(1, 2, 3)(4) = 10;
add(1)(2)(3)(4)(5) = 15;
function add() {
var _args
= Array
.prototype
.slice
.call(arguments
);
var _adder = function() {
_args
.push(...arguments
);
return _adder
;
};
_adder
.toString = function () {
return _args
.reduce(function (a
, b
) {
return a
+ b
;
});
}
return _adder
;
}
add(1)(2)(3).toString()
add(1, 2, 3)(4).toString()
add(1)(2)(3)(4)(5).toString()
add(2, 6)(1).toString()
本文转载自: https://www.jianshu.com/p/2975c25e4d71