函数柯里化到底是什么?有什么好处?进来跟我一起揭秘呀

1,481 阅读3分钟

这是我参与 8 月更文挑战的第 5 天,活动详情查看: 8月更文挑战

柯里化:把接收多个参数的函数变换成接收一个单一参数的函数(单一参数为多个参数中的第一个)并且返回接受余下的参数而且返回结果的新函数,即f(a, b, c) 转换为可调用的 f(a)(b)(c)

函数柯里化思想:一个JS预处理的思想,降低通用性,提高适用性。

特点:

  • 参数复用:需要输入多个参数,最终只需输入一个,其余通过arguments来获取
  • 提前返回:避免重复去判断某一条件是否符合,不符合则return 不再继续执行下面的操作
  • 延迟执行:避免重复的去执行程序,等真正需要结果的时候再执行
// 普通的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

高级柯里化实现

function curry (fn, currArgs) {
    return function() {
        let args = [].slice.call(arguments);
        // 首次调用时,若未提供最后一个参数currArgs,则不用进行args的拼接
        if (currArgs !== undefined) {
            args = args.concat(currArgs);
        }
        // 递归调用
        if (args.length < fn.length) {
            return curry(fn, args);
        }
        // 递归出口
        return fn.apply(null, args);
    }
}

或者
function currying(fn, ...args) {
  if (args.length >= fn.length) {
    return fn(...args);
  } else {
    return (...args2) => currying(fn, ...args, ...args2);
  }
}

function sum(a, b, c) {
  return a + b + c;
}

let curriedSum = curry(sum);

alert( curriedSum(1, 2, 3) ); // 6,仍然可以被正常调用
alert( curriedSum(1)(2,3) ); // 6,对第一个参数的柯里化
alert( curriedSum(1)(2)(3) ); // 6,全柯里化

柯里化使用场景

1. 参数复用

减少重复传递不变的部分参数

function uri (protocol,hostname,pathname){
    return `${protocol}${hostname}${pathname}`;
}

const uri1 = uri("https://","www.site.com","/page1");
const uri2 = uri("https://","www.site.com","/page2");
console.log(uri1);
console.log(uri2);


// 转换为柯里化函数
function uri_curring (protocol){
    return function(hostname,pathname){
        return `${protocol}${hostname}${pathname}`;
    }
}

const uri_https = uri_curring("https://");
console.log(uri_https);
const uri1 = uri_https("www.csdn.net","/page1");
const uri2 = uri_https("www.baidu.com","/page2");
console.log(uri1); // "https://www.csdn.net/page1"
console.log(uri2); // "https://www.baidu.com/page2"

2. 提前确认

//事件监听:性能上会差一点,因为如果是在低版本的IE浏览器上每一次都会运行if()语句,产生了不必要的性能开销.
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);
            }
        };
    }
})();

//换一种写法可能比较好理解一点,上面就是把isSupport这个参数给先确定下来了
var on = function(isSupport, element, event, handler) {
    isSupport = isSupport || document.addEventListener;
    if (isSupport) {
        return element.addEventListener(event, handler, false);
    } else {
        return element.attachEvent('on' + event, handler);
    }
}

3. 延迟运行

延迟执行也是 Currying 的一个重要使用场景,同样 bind 和箭头函数也能实现同样的功能。

function hello(name) {
    console.log('Hello, ' + name);
}
setTimeout(hello('dreamapple'), 3600); //立即执行,不会在3.6s后执行


setTimeout(function() {
    hello('dreamapple');
}, 3600); // 3.6s 后执行
setTimeout(()=> {
    hello('dreamapple');
}, 3600);  // 3.6s 后执行

//bind延迟执行: bind 方法需要强制绑定 context,也就是 bind 的第一个参数会作为原函数运行时的 this 指向
setTimeout(hello.bind(this, 'dreamapple'), 3600); // 3.6s 之后执行函数

//柯里化 延迟执行
function curryingHelper(fn) {
    var _args = Array.prototype.slice.call(arguments, 1);
    return function() {
        var _newArgs = Array.prototype.slice.call(arguments);
        var _totalArgs = _args.concat(_newArgs);
        return fn.apply(this, _totalArgs);
    }
}
setTimeout(curryingHelper(hello, 'dreamapple'), 3600); // 3.6s 之后执行函数

经典题

确定参数的函数柯里化实现

function sum(a,b,c,d){
    return a+b+c+d
}
function curry(fn){
    return function sum(...args){
        if(args.length<fn.length){ // 判断接受的参数是否小于函数的参数长度
           return function(){  // 参数不够长度,再次接受传递参数
                return sum(...args,...arguments)  
           }
        }
        return fn(...args)// 不要求改变this,
    }
}
let curried = curry(sum)
console.log(curried(1)(2)(3)(4))//10
console.log(curried(1,2)(2,4)) //9

不定参数 sum(1)(2)(3)(4)(5)...(n)


function curry(fn){
    let parmas = []
    return function sum(...args){
        if(args.length){//判断是否有参数
            parmas = [...parmas,...args]
            return sum
        }
        return fn(parmas)
    }
}
function add(arr){
   return arr.reduce((acc,item)=>{
        return acc+item
    })
}
 
let curried = curry(add)
console.log(curried(1)(2)(3)(4)(10,20)())//40
// 注意最后的调用用方式,()调用不传递参数,会跳出判断,调用累加函数
//在方法内部定义一个tostring方法,返回计算的值
function curry(...args){
	let parmas = args
    function sum(){
        parmas = [...parmas,...arguments]
        return  sum
    }
    sum.toString=function(){
       return parmas.reduce((acc,item)=>{
       	 return acc+item
       })
       
    }
    return sum
}
console.log(curry(1)(2)(3)(10)(10,20).toString()) // 40