手写 柯里化函数
什么是柯里化
把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回 接受余下的参数而且返回结果 的新函数的技术。
核心思想:把多参数传入的函数拆成单参数函数,内部再返回调用下一个单参数(或部分)函数,依次处理剩余的参数。
// 简单使用
function add(x, y) {
return x + y
}
// Currying后
function curryingAdd(x) {
return function (y) {
return x + y
}
}
add(1, 2) // 3
curryingAdd(1)(2) // 3
柯里化的应用
参数复用
// 普通正则验证字符串
reg.test(txt);
// 函数封装
function check(reg, txt){
return reg.test(txt);
}
check(/\d+/g, 'test') //false
check(/[a-z]+/g, 'test') //true
// 柯里化
function curringCheck(reg) {
return function(txt){
return reg.test(txt);
}
}
var hasNumber = curryingCheck(/\d+/g)
var hasLetter = curryingCheck(/[a-z]+/g)
hasNumber('test1') // true
hasNumber('testtest') // false
hasLetter('21212') // false
如果有很多地方需要校验是否有数字,或者是否是字母组成,其实就是需要将第一个参数reg进行复用,这样别的地方就可以直接调用hasNumber,hasLetter等函数。
提前确认
// 每次调用这个方法都要判断一下浏览器兼容哪个方法
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);
}
};
}
})();
在做项目的过程中,经常会封装一些dom操作,第一种写法是比较常见的。第二种写法相对第一种先自执行然后再返回一个新的函数,提前确定了会走哪一个方法,不用等到使用的时候再进行判断。
延迟运行
Function.prototype.bind = function (context) {
var _this = this
var args = Array.prototype.slice.call(arguments, 1)
return function() {
return _this.apply(context, args)
}
}
以上是简单的bind实现,函数的主体本身不执行,而是将第一个参数设置为函数执行的上下文,其他参数依次传递给调用方法,动态创建返回一个新函数。
柯里化通用实现
// 多参数传递
function progressCurrying(fn, args) {
var _this = this
var len = fn.length;
var args = args || [];
return function() {
// 实际调用的时候传递的参数
// Array.prototype.slice.call可以用来将一个类数组(Array-like)对象/集合转换成一个新数组
var _args = Array.prototype.slice.call(arguments);
// 将数组型参数扩展成参数列表,合并两个数组
Array.prototype.push.apply(_args, args);
// 如果参数个数小于最初的fn.length,则递归调用,继续收集参数,返回函数
if (_args.length < len) {
return progressCurrying.call(_this, fn, _args);
}
// 参数收集完毕,则执行fn
return fn.apply(this, _args);
}
}
柯里化的性能消耗
- 存取arguments对象通常要比存取命名参数要慢一点
- 一些老版本的浏览器在arguments.length的实现上是相当慢的
- 使用fn.call 和 fn.apply通常比直接调用fn慢点
- 创建大量嵌套作用域和闭包函数会带来花销,无论是在内存还是速度上
但是在大部分的应用中,主要的性能瓶颈是在操作DOM节点上,这些js性能损耗基本上是可以忽略不计的,但也不能滥用。