柯里化

273 阅读3分钟

有3个常见作用:1. 参数复用;2. 提前返回;3. 延迟计算/运行

1 参数复用

let fun = (function (...arg) {
    let args1 = arg
    return function (...a) {
         let args2 = args1.concat(a)
        console.log(args2)
    }
})('提前设定参数')

fun('后添加参数')

2 提前返回逻辑,不用在程序执行时通过 if else 在返回相应逻辑。如果3次执行,那么只需要判断一次,否则判断3次

let p = 0
let fun = (function () {
    if (p === 0) {
        return function () {
            // 100行逻辑
        }
    } else {
        return function () {
            // 100行逻辑
        }
    }
})()

fun(1)
fun(1)
fun(1)

3 延迟计算/运行

const fun = (function (...arge) {
    return function () {
        if (arguments.length === 0) {
            console.log(arge.reduce((a,b) => a + b))
        } else {
            let arge2 = Array.from(arguments)
            arge = arge.concat(arge2)
            console.log(arge)
        }
    }
})()
fun(1)
fun(2)
fun(3)
fun()

4 add(1,2,3)(1)(2)(3)(4,5,6)(7,8)()

function add (...arge) {
    console.log(arge)
    return function _add (...arge2) {
        if (arge2.length === 0) {
            console.log(arge.reduce((a, b) => a + b))
        } else {
            arge = arge.concat(arge2)
            return _add
        }
    }

}
add(1,2,3)(1)(2)(3)(4,5,6)(7,8)() // 42

5 通用的函数来对普通函数进行柯里化

可以看出来,柯里化其实是有特点的,需要一个闭包保存参数,一个函数来进行递归,这种模式是可以通过一个包装函数,对一些基本的函数进行包装之后的函数具有curry的特性。实现如下: // 通用的函数柯里化构造方法

function curry(func){
  //新建args保存参数,注意,第一个参数应该是要柯里化的函数,所以args里面去掉第一个
  var args = [].slice.call(arguments,1);
  //新建_func函数作为返回值
  var _func =  function(){
      //参数长度为0,执行func函数,完成该函数的功能
      if(arguments.length === 0){
          return func.apply(this,args);
      }else {
          //否则,存储参数到闭包中,返回本函数
          [].push.apply(args,arguments);
          return _func;
      }
  }
  return _func;
}

function add(){
  return [].reduce.call(arguments,function(a,b){return a+b});
}
console.log(curry(add,1,2,3)(1)(2)(3,4,5,5)(5,6,6,7,8,8)(1)(1)(1)());//69

6 优化

let fun = function (a) {
    大量计算
    return function (b) {
        大量计算
    }
}
let fun2 = fun(a)(b)

fun2(3)
fun2(4)

7 提前返回

let addEvent = function (el, type, fn, capture) {
  if (window.addEventListener) {
    el.addEventListener(type, fn)
  } else if (window.attachEvent) {
    el.attachEvent("on", type, fn)
  }
}
addEvent(div, "click", fn, false)
优化
let addEvent2 = function () {
  if (window.addEventListener) {
    return function ( el, type, fn, capture ) {
      el.addEventListener(type, fn)
    }
  } else if (window.attachEvent) {
    return function ( el, type, fn, capture ) {
      el.attachEvent("on", type, fn)
    }
  }
}
let fun = addEvent2()
fun(div, "click", fn, false)

8 延迟执行 不定参数

let fun1 = function () {
  let all = []
  return function m () {
    if (arguments.length) {
      all = all.concat(Array.from(arguments))
      return m
    } else {
      console.log(all.reduce((a,b) => a + b))
    }
  }
}

let f = fun1()
f(1)(2)(3)
f(1)(2)(3)()

9 参数复用

function isArray(obj) { 
    return Object.prototype.toString.call(obj) === '[object Array]';
}

function isNumber(obj) {
    return Object.prototype.toString.call(obj) === '[object Number]';
}

function isString(obj) {
    return Object.prototype.toString.call(obj) === '[object String]';
}

// Test
isArray([1, 2, 3]); // true
isNumber(123); // true
isString('123'); // true

const toStr = Function.prototype.call.bind(Object.prototype.toString);

// 改造前
[1, 2, 3].toString(); // "1,2,3"
'123'.toString(); // "123"
123.toString(); // SyntaxError: Invalid or unexpected token
Object(123).toString(); // "123"

// 改造后
toStr([1, 2, 3]); 	// "[object Array]"
toStr('123'); 		// "[object String]"
toStr(123); 		// "[object Number]"
toStr(Object(123)); // "[object Number]"

10 实现 currying 函数 (通用)

function currying(fn, length) {
  length = length || fn.length; 	// 注释 1
  return function (...args) {			// 注释 2
    return args.length >= length	// 注释 3
    	? fn.apply(this, args)			// 注释 4
      : currying(fn.bind(this, ...args), length - args.length) // 注释 5
  }
}

// Test
const fn = currying(function(a, b, c) {
    console.log([a, b, c]);
});

fn("a", "b", "c") // ["a", "b", "c"]
fn("a", "b")("c") // ["a", "b", "c"]
fn("a")("b")("c") // ["a", "b", "c"]
fn("a")("b", "c") // ["a", "b", "c"]

注释 1:第一次调用获取函数 fn 参数的长度,后续调用获取 fn 剩余参数的长度

注释 2:currying 包裹之后返回一个新函数,接收参数为 ...args

注释 3:新函数接收的参数长度是否大于等于 fn 剩余参数需要接收的长度

注释 4:满足要求,执行 fn 函数,传入新函数的参数

注释 5:不满足要求,递归 currying 函数,新的 fn 为 bind 返回的新函数(bind 绑定了 ...args 参数,未执行),新的 length 为 fn 剩余参数的长度