前言
- 什么是函数柯里化
什么是函数柯里化
在计算机科学中,柯里化(英语:Currying),又译为卡瑞化或加里化,是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。
在直觉上,柯里化声称如果你固定某些参数,你将得到接受余下参数的一个函数。
柯里化(currying)又称部分求值。一个柯里化的函数首先会接受一些参数,接受了这些参数之后,该函数并不会立即求值,而是继续返回另外一个函数,刚才传入的参数在函数形成的闭包中被保存起来。待到函数被真正需要求值的时候,之前传入的所有参数都会被一次性用于求值。
柯里化快速入门
假设我们有一个求取两个数之和的函数:
function add(x, y) {
return x + y;
}
console.log(add(1, 2)); // 3
console.log(add(5, 7)); // 12
现在,我们对其进行柯里化,如下:
function add(x) {
return function (y) {
return x + y;
}
}
console.log(add(1)(2)); // 3
console.log(add(5)(7)); // 3
在上面的代码中,我们对 add 函数进行了柯里化改造,只接受一个参数,但是返回的也不是值了,而是返回一个函数,这个函数也接收一个参数,然后利用闭包的特性,可以访问到最开始传入的 x 的值,最终返回 x 和 y 的和。
所以,通过上面的这个示例,我们能够体会到前面所说的柯里化函数的特点:
一个柯里化的函数首先会接受一些参数,接受了这些参数之后,该函数并不会立即求值,而是继续返回另外一个函数,刚才传入的参数在函数形成的闭包中被保存起来。待到函数被真正需要求值的时候,之前传入的所有参数都会被一次性用于求值。
函数柯里化实际应用
参数复用(固定参数)
// 正常正则验证字符串 reg.test(txt)
// 函数封装后
function check(reg, txt) {
return reg.test(txt)
}
// 即使是相同的正则表达式,也需要重新传递一次
console.log(check(/\d+/g, 'test1')); // true
console.log(check(/\d+/g, 'testtest')); // false
console.log(check(/[a-z]+/g, 'test')); // true
// Currying后
function curryingCheck(reg) {
return function (txt) {
return reg.test(txt)
}
}
// 正则表达式通过闭包保存了起来
var hasNumber = curryingCheck(/\d+/g)
var hasLetter = curryingCheck(/[a-z]+/g)
console.log(hasNumber('test1')); // true
console.log(hasNumber('testtest')); // false
console.log(hasLetter('21212')); // false
上面的示例是一个正则的校验,正常来说直接调用 check 函数就可以了,但是如果我有很多地方都要校验是否有数字,其实就是需要将第一个参数 reg 进行复用,这样别的地方就能够直接调用 hasNumber、hasLetter 等函数,让参数能够复用,调用起来也更方便。
提前确认
/**
*
* @param {要绑定事件的 DOM 元素} element
* @param {绑定什么事件} event
* @param {事件处理函数} handler
*/
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);
}
}
}
on(div, 'click', function(){})
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);
}
};
}
})();
on(div, 'click', function(){})
我们在做项目的过程中,封装一些 DOM 操作可以说再常见不过,上面第一种写法也是比较常见,但是我们看看第二种写法,它相对于第一种写法就是自执行然后返回一个新的函数,这样其实就是提前确定了会走哪一个方法,避免每次都进行判断。
封装通用柯里化函数
function curry() {
// 获取要执行的函数
var fn = arguments[0];
// 获取传递的参数,构成一个参数数组
var args = Array.prototype.slice.call(arguments, 1);
// 如果传递的参数已经等于执行函数所需的参数数量
if (args.length === fn.length) {
return fn.apply(this, args);
} else {
// 参数不够向外界返回的函数
var _curry = function () {
// 将新接收到的参数推入到参数数组中
args.push(...arguments);
if (args.length === fn.length) {
return fn.apply(this, args);
} else {
return _curry;
}
}
return _curry;
}
}
对上面的代码进行测试:
// 测试 1
function add(a, b, c) {
return a + b + c;
}
console.log(curry(add)(1)(2)(3)); // 6
console.log(curry(add, 1)(2)(3)); // 6
console.log(curry(add, 1, 2, 3)); // 6
console.log(curry(add, 1)(3, 4)); // 8
var addCurrying = curry(add)(2);
console.log(addCurrying(7)(8)); // 17
// 测试 2
function check(reg, txt) {
return reg.test(txt)
}
var hasNumber = curry(check)(/\d+/g);
console.log(hasNumber('test1'));// true
一道经典的柯里化面试题
实现一个 add 方法,使计算结果能够满足如下预期:
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);
if ([...arguments].length === 0) {
return args.reduce((a, b) => a + b)
}
var _add = function () {
if ([...arguments].length === 0) {
return args.reduce((a, b) => a + b)
}
args.push(...arguments);
return _add;
}
return _add;
}
console.log(add(1)(2)(3)()); // 6
console.log(add(1, 2, 3)(4)()); // 10
console.log(add(1)(2)(3)(4)(5)()); // 15
console.log(add(2, 6)(1)()); // 9
总结
- 什么是函数柯里化
一个柯里化的函数首先会接受一些参数,接受了这些参数之后,该函数并不会立即求值,而是继续返回另外一个函数,刚才传入的参数在函数形成的闭包中被保存起来。待到函数被真正需要求值的时候,之前传入的所有参数都会被一次性用于求值。