高级函数技巧-函数柯里化
维基百科上说道:柯里化,英语:Currying(果然是满满的英译中的既视感),是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。
柯里化的实用性
参数复用
【通用函数】解决了兼容性问题,但同时也会再来,使用的不便利性,不同的应用场景往,要传递很多参数,以达到解决特定问题的目的。有时候应用中,同一种规则可能会反复使用,这就可能会造成代码的重复性。
1 | // 正常正则验证字符串 reg.test(txt) |
上面的示例是一个正则的校验,正常来说直接调用 check 函数就可以了,但是如果我有很多地方都要校验是否有数字,其实就是需要将第一个参数 reg 进行复用,这样别的地方就能够直接调用 hasNumber,hasLetter 等函数,让参数能够复用,调用起来也更方便。
提高适用性
柯里化不仅仅是提高了代码的合理性,更重的它突出一种思想—降低适用范围,提高适用性
1 | function Ajax() { |
延迟执行
1 | Function.prototype.bind = function (context) { |
固定易变因素
柯里化特性决定了它这应用场景。提前把易变因素,传参固定下来,生成一个更明确的应用函数。
bind 函数用以固定 this 这个易变对象
1 | Function.prototype.bind = function (context) { |
封装一些 dom 操作
1 | var on = function (isSupport, element, event, handler) { |
通用的封装方法
1 | function curry(fn, args) { |
性能
在大部分应用中,主要的性能瓶颈是在操作 DOM 节点上,这 js 的性能损耗基本是可以忽略不计的,所以 curry 是可以直接放心的使用。
- 存取 arguments 对象通常要比存取命名参数要慢一点
- 一些老版本的浏览器在
arguments.length
的实现上是相当慢的 - 使用
fn.apply( … )
和fn.call( … )
通常比直接调用 fn( … ) 稍微慢点 - 创建大量嵌套作用域和闭包函数会带来花销,无论是在内存还是速度上
额外开销
函数柯里化可以用来构建复杂的算法 和 功能, 但是滥用也会带来额外的开销。
从上面实现部分的代码中,可以看到,使用柯里化函数,离不开闭包, arguments, 递归。
- 闭包,函数中的变量都保存在内存中,内存消耗大,有可能导致内存泄漏。
- 递归,效率非常差,
- arguments, 变量存取慢,访问性很差
经典面试题
实现
add(1)(2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14// 普通的add函数
function add(x, y) {
return x + y;
}
add(1, 2); // 3
// Currying后
function curryingAdd(x) {
return function (y) {
return x + y;
};
}
curryingAdd(1)(2); // 3实现一个 add 方法,使计算结果能够满足如下预期:
1
2
3add(1)(2)(3) = 6;
add(1, 2, 3)(4) = 10;
add(1)(2)(3)(4)(5) = 15;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23function add() {
// 第一次执行时,定义一个数组专门用来存储所有的参数
var _args = Array.prototype.slice.call(arguments);
// 在内部声明一个函数,利用闭包的特性保存_args并收集所有的参数值
var _adder = function () {
_args.push(...arguments);
return _adder;
};
// 利用toString隐式转换的特性,当最后执行时隐式转换,并计算最终的值返回
_adder.toString = function () {
return _args.reduce(function (a, b) {
return a + b;
});
};
return _adder;
}
add(1)(2)(3); // 6
add(1, 2, 3)(4); // 10
add(1)(2)(3)(4)(5); // 15
add(2, 6)(1); // 9