函数柯里化Currying

6,240 阅读6分钟

什么是柯里化

在计算机科学中,柯里化(Currying)是一种技术(技巧),能够把本来接受 n 个参数的函数A,转换成只接收一个参数的函数B(B中的唯一参数,就是A的多个参数中的 第一个 参数)。

然后新函数B返回的,还是一个函数,记为C(注意原A中返回的不一定是啥)。这个C函数又只能接收一个参数(即为A函数的 第二个 参数)......依次不断往复,直到返回一个接收A的第 n 个参数的函数F,F中判断这是最后一个函数了,执行,然后返回最终的值。

很绕口,看个例子:

// 我们想要实现一个把参数相加,返回和的函数
function adder(a, b) {
	return a + b;
}
adder(4,6);
// 结果为:10

当然也可以这样写(非柯里化)

function adder() {
	var sum = 0;
	for(var i=0;i<arguments.length;i++)
		sum += arguments[i];
	return sum;
}
adder(4,6);
// 10

用柯里化实现

var adder = function(num) {
    var n = num;   // 对应的为参数4
    return function(y) {
        return n + y; // y为6   返回 4+6
    }
}
adder(4)(6)    // 10

从上面可以看到,本来adder是传2个参数adder(4, 6),柯里化的方式后,就成为了adder(4)(6),即每次接受一个参数并放回一个函数,然后链式的执行。。。

可以看到,adder函数中有着内部函数,内部函数一直引用着n,这就形成了一个闭包,所以柯里化是闭包的应用之一,面试时直接可以答~~~

进阶

从上面可以大概了解了,柯里化就是,

把原来的函数 adder(3,5,6,7,8,9) ==> adder(3)(5)(6)(7)(8)(9)()

注意:最后一次调用没有参数(),所以adder中可以通过判断是否有参数,去判断我是要继续相加,还是返回求和值了

或是这样也可以

adder(3)
adder(5,6,7)
adder(8,9)
adder() // 无参数时返回和

那么这么做的好处是什么呢?

  • 延迟计算,何时想要结果了,直接adder()就行了
  • 通常也成为部分求值,给函数分步传递参数,逐步缩小函数的适用范围,逐步求解的过程。

所以我们可以在写一个函数的时候,用柯里化的思想去写;但是我们也可以对任何一个原有的函数,将他柯里化了,变成柯里化的思想与形式,看思路~

例:

一个原有的函数

// 不知道这test函数是啥功能,不用管他是干啥的,但很显然不是简单的求和,
// 所以你不能继续用 循环 arguments 了。。。
function test(name, id, num, score, height) {
    
}

将他柯里化,就是要将他

test("chen", 45, 789, 284.2, 178) ==> test2("chen")(45)(789)(284.2)(178)()

其中test2就是经过柯里化包装后的test

包装的思想是

  1. test2中建个数组arr,多次调用后,arr就成了 ["chen", 45, 789, 284.2, 178]
  2. 当没参数时,执行test.apply(arr) ,,, 想起apply干啥的不
  3. 所以,其实就是把一堆参数给存起来先,然后最后再执行一次执行test

通用封装代码如下,一定要搞懂!

实际应用

如果还是没觉得有啥用,看两个实际的例子

1. 浏览器事件

还记得事件要区分IE和非IE不了

var addEvent = function(el, type, fn, capture) {
    if(window.addEventListener) {
        el.addEventListener(type, function(e) {
            fn.call(el, e)
        }, capture)
    } else {
        el.attachEvent('on'+type, function(e) {
            fn.call(el, e)
        })
    }
}

也就是说,我们会调用addEvent,但是每次的话都得执行内部的if...else....

所以可以用柯里化,改成

var curEvent = (function() {
    if(window.addEventListener) {
        return function(el, sType, fn, capture) { // return funtion
            el.addEventListener(sType, function() {
                fn.call(el, e)
            }, capture)
        }
    } else {
        return function(el, sType, fn) {
            el.attachEvent('on'+sType, function(e) {
                fn.call(el, e)
            })
        }
    }
})

var addEvent = curEvent();  // addEvent 这回得到的,就是if..else...里面的那个returnfunction,所以只需要curEvent()执行一遍判断了if..else,其他时候就都不需要判断了

addEvent(elem)

2. 函数的bind

我们总会看到,函数可以这样 test.bind(this),其中bind函数就是柯里化的思想

Funtion.prototype.bind()

var foo = {
    x: 888
}
var bar = function(e) {
    console.log(this.x, e)
}
Function.prototype.testBind = function(scope) {
    var fn = this;    // 指向的 bar
    return function() {
        return fn.apply(scope, [].slice.call(arguments))
    }
}

var test = bar.testBind(foo)    // 绑定 foo ,延迟执行
console.log(test)
test(2323)    //  执行, 结果是 888  2323

将传入的第一个参数(foo),当作之后函数的执行上下文,,,其他参数 再传给 调用的方法(函数本身不执行,只进行了bind绑定,以后函数执行的时候,相当于了 延迟执行) , 所以相当于,预先绑定了对象并返回了函数,之后再执行函数,符合柯里化。

3.Redux中的 applyMiddle 中间件原理

let store = applyMiddleware(middleware1,middleware2)(createStore)(rootReducer);

export default function createLogger({ getState }) {
      return (next) =>  // return function
        (action) => {
              const console = window.console;
              const prevState = getState();
              const returnValue = next(action);
              const nextState = getState();
              const actionType = String(action.type);
              const message = `action ${actionType}`;

              console.log(`%c prev state`, `color: #9E9E9E`, prevState);
              console.log(`%c action`, `color: #03A9F4`, action);
              console.log(`%c next state`, `color: #4CAF50`, nextState);
              return returnValue;
    };
}

作用总结

  • 一个js预先处理的思想;利用函数执行时可以形成一个不销毁的作用域的原理,把需要预先处理的内容都存储到这个不销毁的作用域中,并且返回一个小函数,以后我们执行的都是这个小函数,小函数中把之前预先存储的值进行相关操作处理。比如说一个函数在移动端和PC端双端运行,双端前5行代码是重合的,后面的代码不同,那么就可以把前5行代码搞成柯里化的形式,也不是说代码,就是重合的部分功能可以前置
  • 通常也成为部分求值,给函数分步传递参数,逐步缩小函数的适用范围,逐步求解的过程。
  • 预处理
  • 延迟计算
  • 可以传递需要的参数,等到何时想要结果,再一并计算
  • 参数复用
  • 有些参数相同,只需要传递一遍即可,不需要每次都传,太繁琐。例如 bind
  • 动态创建函数。这可以是在部分计算出结果后,在此基础上动态生成新的函数,处理后面的业务,这样省略了重复计算。或者可以通过将要传入调用函数的参数子集,部分应用到函数中,从而动态创造出一个新函数,这个新函数保存了重复传入的参数(以后不必每次都传)。例如,浏览器添加事件的辅助方法。

最后:若有错误之处,还请见谅,提出后会马上修改~~~

转载请注明出处,谢谢~~

下一篇:DOM的解析和渲染与JS、CSS的关系