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

参考资料

https://www.jianshu.com/p/2975c25e4d71

https://zhuanlan.zhihu.com/p/296852112

相关文章

前言 做过web项目开发的人对layer弹层组件肯定不陌生,作为l...
前言 前端表单校验是过滤无效数据、假数据、有毒数据的第一步...
前言 图片上传是web项目常见的需求,我基于之前的博客的代码...
前言 导出Excel文件这个功能,通常都是在后端实现返回前端一...
前言 众所周知,js是单线程的,从上往下,从左往右依次执行,...
前言 项目开发中,我们可能会碰到这样的需求:select标签,禁...