JavaScript函数柯里化
柯里化,是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。
常用场景
- 参数复用
- 提前返回
- 延迟计算/运行
// 普通的add函数
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)
*
* 需求:
* 验证字符串中是否含有数字或者字母
*
*/
//直接调用正则验证
/\d+/g.test('hello') //false
/[a-z]+/g.test('hello') //true
// 常规函数封装
function check(reg, txt) {
return reg.test(txt)
}
check(/\d+/g, 'hello') //false
check(/[a-z]+/g, 'hello') //true
// Currying 注:项目中需要多次用到规则函数时,将该函数定义为全局函数,函数复用性更好。
function curryingCheck(reg) {
return function(txt) {
return reg.test(txt)
}
}
/**
* hasNumber验证全局是否含有数字
* hasLetter验证全局是否含有字母
* hasNumber,hasLetter此时已经定义为对应的工具函数,调用时只需要传递一个参数
* 使用起来更加方便而且语义化更强,代码复用度更高
*
**/
const hasNumber = curryingCheck(/\d+/g)
const hasLetter = curryingCheck(/[a-z]+/g)
hasNumber('hello') // false
hasLetter('hello') // true
提前确认
/**
*
* 兼容现代浏览器以及IE浏览器的事件添加方法
*
**/
//常规写法
//问题:每次使用addEvent为元素添加事件的时候,都会判断一次。
var addEvent = 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);
}
}
}
//Currying
//初始addEvent的执行其实值实现了部分的应用,而剩余的参数应用都是其返回函数实现。
var addEvent = (function() {
if (document.addEventListener) {
return function(element, event, handler) {
if (element && event && handler) {
element.addEventListener(event, handler, false);
}
};
} else {
return function( event, handler) {
if (element && event && handler) {
element.attachEvent('on' + event, handler);
}
};
}
})();
//不使用自启动函数写法
var addEvent = function() {
const isSupport = document.addEventListener;
if (isSupport) {
return function(element, event, handler) {
if (element && event && handler) {
element.addEventListener(event, handler, false);
}
}
} else {
return function( event, handler) {
if (element && event && handler) {
element.attachEvent('on' + event, handler);
}
};
}
}
延迟运行
/**
*
* 函数常用的bind方法实质就是延迟执行
*
**/
Function.prototype.bind = function (context) {
var _this = this
var args = Array.prototype.slice.call(arguments, 1)
return function() {
return _this.apply(context, args)
}
}
经典习题
// 实现一个add方法,使计算结果能够满足如下预期:
add(1)(2)(3)()= 6;
add(1, 2, 3)(4)() = 10;
add(1)(2)(3)(4)(5)() = 15;
// 定义参数累加方法
function adder(...args) {
return args.reduce( (total, currentVal) => {
total += currentVal;
return total;
})
}
function addCurry(fn) {
// _allargs 存储所有的参数
// _tempargs 传递给累加方法的复制所有参数的临时参数
// _tempargs 作用在收集到所有参数时使_allargs清空参数,否则重复调用时,_allargs会将上一次调用时参数与此次调用的参数合拼,造成错误的结果。
var _allargs = [];
var _tempargs= [];
return function reFn(...args){
if(args.length === 0){
_tempargs = _allargs;
_allargs = [];
return fn.apply(this,_tempargs);
}
else{
_allargs = _allargs.concat(args)
return reFn;
}
}
}
console.log(add(1, 2, 3)(4)()); // 10
console.log(add(5)(6)(7)(8)(9)()); // 35
/*
* 可重复调用写法
*
* 知识点:对象(包括数组,对象,函数等)参与原始运算如算术或逻辑运算时,会无参调用其toString或者valueOf方法得到 * 一个原始值,然后用这个原始值参与运算。
**
*/
function add(...args) {
// 将参数绑定到add上
// 此时f其实还是add函数,但已经固定了一些参数,所以并不是原来的add函数
// 用bind返回新函数以保证满足**柯里化保留参数**的特性
var f = add.bind(null/*this不用绑定*/, ...args)
// 重新实现这个bound add函数的toString方法
// f参与运算应该被当成args的和,与f自己再接收的参数无关
// 考虑到lazy的特性,还是需要时再计算,但又没了缓存,每次用都会重新计算
// 如果有需要,可以改成有缓存的版本
f.toString = () => {
return args.reduce((a, b) => a + b, 0)
}
return f;
}
// 考虑到add可能直接被用于运算中,可以加上这句
add.toString = () => 0
/*
注意以上代码中的add,add3,add8,add8p,add9都是不同的函数,且每个函数要加的数是不一样的。
*/
const add3 = add(0, 1)(2) // add3的功能是对传入的数值加3并返回
console.log( add3(2) + 0 ) // log出5
const add8 = add3(1)(2)(2) // add8由add3的持续调用得到
const add8p = add3(5) // 另一种方式得到add8,注意两个add8不是同一个函数,起名add8p
const add9 = add8(1) // 由add8再传入得到add9函数
console.log( add9(1) + 3 ) // log出13
console.log( add8(1) + 3 ) // log出12