JavaScript柯里化

函数柯里化(curry)能够简单的理解是将具备多个参数的函数,转换成具备较少的参数的函数的过程,具体将具备多个参数的函数转换成一系列嵌套函数,使用一部分参数调用柯里化函数返回一个新函数,每次返回的函数处理剩余的参数。javascript

从一个简单函数理解柯里化过程

咱们经过一个经常使用的函数举例说明柯里化函数的使用。html

function multiplyabc{ 
    return a * b * c; 
}

multiply(2,2,5// 20
复制代码

这个简单的函数定义了三个参数,参数相乘返回运算结果。java

咱们将上面的函数重构成柯里化结构的函数,用以说明柯里化的使用方法。git

function multiplyabc{ 
    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)柯里化先后上下文堆栈的建立过程:

  1. multiply(a, b, c) 非柯里化
  • 执行multiply函数,首先暂停全局上下文
  • 在全局上下文堆栈建立新的上下文
  • 新的上下文经过scopeChain关联到全局上下文
  • 激活新的上下文
  • 新的上下文堆栈执行完毕
  • 新的上下文被弹出
  • 全局上下文堆栈处于激活状态

当函数调用建立的上下文处于激活状态是的上下文堆栈状况:

堆栈层级 上下文 状态
2 (栈顶) multiply 激活
1 全局上下文 被暂停
  1. multiply柯里化

同上的分析过程,咱们能够判断multiply柯里化函数在multiply_sec(5)运行时的堆栈状况。

堆栈层级 上下文 状态
4(栈顶) multiply_sec 激活
3 multiply_fir 被暂停
2 multiply 被暂停
1 全局上下文 被暂停

经过对上下文的建立过程的分析,咱们能够理解嵌套深的函数会建立更多的上下文帧,不合理的柯里话函数会致使更多的内存消耗,咱们在使用柯里化的时候须要了解清楚函数执行上下文的执行原理,合理的规划实现方式。

咱们也能够从新定义JS的柯里化:柯里化是一种词法做用域,返回的函数是一个接受后续参数的包装器。

参考

相关文章
相关标签/搜索