函数柯里化(curry)能够简单的理解是将具备多个参数的函数,转换成具备较少的参数的函数的过程,具体将具备多个参数的函数转换成一系列嵌套函数,使用一部分参数调用柯里化函数返回一个新函数,每次返回的函数处理剩余的参数。javascript
咱们经过一个经常使用的函数举例说明柯里化函数的使用。html
function multiply(a,b,c){
return a * b * c;
}
multiply(2,2,5)// 20
复制代码
这个简单的函数定义了三个参数,参数相乘返回运算结果。java
咱们将上面的函数重构成柯里化结构的函数,用以说明柯里化的使用方法。git
function multiply(a,b,c){
return a=> {
return b=> {
return c => {
return a * b * c;
}
}
}
}
multiply(2)(2)(5) // 20
复制代码
经过柯里化函数,咱们将一个功能改形成一系列的功能,将以前须要一次传递的多个参数,分为三次传递,每次传递的函数做为下一个函数的内链调用。编程
为了便于理解函数柯里化的调用,咱们能够将上面的柯里化函数multiply(2)(2)(5) 调用过程分开书写。segmentfault
const multiply_fir = multiply(2);
const multiply_sec = multiply_fir(2);
const result = multiply_sec(5);
console.log(result ); // 20
复制代码
理解了返回函数的内链调用的过程,如今咱们开始实现柯里化函数的内部原理:数组
// 基于ES6原理实现
let curryFun = (fun, length, ...args) => {
let len = args.length;
return len === length ? fun(...args) : curryFun.bind(null, fun, len, ...args);
}
// 测试
let addFun = curryFun(function(){
let sum = 0;
let args = [].slice.call(arguments, 0);
let len = args.length;
for(let i=0;i<len;i++) {
sum += args[i];
}
return sum;
})
let sun = addFun(2,12,19)(198)(100)();
console.log(sun); //331
复制代码
柯里化函数是函数式编程的一种很实用的实践。函数式编程是一种重要的编程范式,最重要的特色是函数做为第一等公民,而且强调函数自己的纯粹性,对于相同的输入参数返回相同的结果没有其余反作用。ide
因此咱们建立的柯里化函数自己是一个纯函数,而且具备本身的特征,能够建立有意义的代码模块。函数式编程
当咱们的函数参数大部分状况下相同的时候,咱们能够利用柯里化解决公共参数复用的问题。函数
const obj = { name: 'test' };
const foo = function (prefix, suffix) {
console.log(prefix + this.name + suffix);
}.bind(obj, 'currying-');
foo('-function'); // currying-test-function
复制代码
bind方法将第一个参数设置成函数执行上下文,其余的参数传递给调用方法。
若是一个值要通过多个函数,才能变成另一个值,就能够把全部中间步骤合并成一个函数,这叫作"函数的合成"(compose)
// ES5写法
const compose = function (f, g) {
return function (x) {
return f(g(x));
};
}
复制代码
经过封装一系列处理步骤,能够减小对中间变量的操做,也是一种代码复用。好比咱们须要操做一个数组,返回的对象是原数组每一个元素上+1后的新的数组,这个需求很简单:
const list = [0, 1, 2, 3];
const list1 = list.map(elem => elem + 1); // => [1, 2, 3, 4]
复制代码
当咱们需求发生变化,须要增长+2的操做结果:
const list = [0, 1, 2, 3];
const list1 = list.map(elem => elem + 1); // => [1, 2, 3, 4]
const list2 = list.map(elem => elem + 2); // => [2, 3, 4, 5]
复制代码
咱们在维护这样一系列的代码的时候,很容易想到将list.map(elem => elem + 2)这样一个过程,进行封装,咱们只须要传递咱们须要操做的参数便可,这样能够提升代码的可读性。
咱们的代码结构以下:
const plus1 = plus(1);
const plus2 = plus(2);
const list = [0, 1, 2, 3];
const list1 = list.map(plus1); // => [1, 2, 3, 4]
const list2 = list.map(plus2); // => [2, 3, 4, 5]
复制代码
咱们只须要实现plus()函数便可完成代码的封装,提升代码的维护性和可读性,JS柯里化能够实现咱们的需求。
function plus(a) {
return function(b) {
return a + b;
}
}
复制代码
首先咱们须要了解JavaScript函数的执行机制的一些基础内容:JS是经过执行上下文堆栈管理函数的执行和变量的做用域。JS的函数调用实际上是在函数执行上下文堆栈上建立新的记录(帧)。
每一个执行上下文帧都须要必定的内存空间,空帧大约须要48字节,每一个参数和局部变量通常须要8字节。
函数柯里化是将函数一次执行的过程,变成嵌套函数执行屡次。根据JS的执行机制咱们能够可以理解函数柯里化,使用到了更多的嵌套函数,致使更多的执行上下文堆栈开销。
JS是单线程的,有且只有一个全局上下文,全局上下文共享,函数在调用的时候首先会在暂停全局上下文,建立新的上下文堆栈,使得新上下文处于激活状态。经过scopeChain建立到父类上下文的连接。激活上下文执行完毕会被弹出,父类上下文从新处于激活状态。
有了这些基础知识咱们能够利用JS执行机制,解释 multiply(a, b, c)柯里化先后上下文堆栈的建立过程:
当函数调用建立的上下文处于激活状态是的上下文堆栈状况:
堆栈层级 | 上下文 | 状态 |
---|---|---|
2 (栈顶) | multiply | 激活 |
1 | 全局上下文 | 被暂停 |
同上的分析过程,咱们能够判断multiply柯里化函数在multiply_sec(5)运行时的堆栈状况。
堆栈层级 | 上下文 | 状态 |
---|---|---|
4(栈顶) | multiply_sec | 激活 |
3 | multiply_fir | 被暂停 |
2 | multiply | 被暂停 |
1 | 全局上下文 | 被暂停 |
经过对上下文的建立过程的分析,咱们能够理解嵌套深的函数会建立更多的上下文帧,不合理的柯里话函数会致使更多的内存消耗,咱们在使用柯里化的时候须要了解清楚函数执行上下文的执行原理,合理的规划实现方式。
咱们也能够从新定义JS的柯里化:柯里化是一种词法做用域,返回的函数是一个接受后续参数的包装器。