引言

如果你用 JavaScript 编写代码,很可能你遇到过闭包这个词,这是一个有用但又常常令人困惑的概念。 但究竟什么是结束?

闭包可以被描述为函数和声明它的词法环境的组合。

但这究竟意味着什么呢? 在创建函数时,词法环境由函数作用域中的任何局部变量组成。 闭包使人们能够引用一个函数的所有局部变量在它们被发现的状态。

这基本上是通过在另一个函数中定义一个函数来实现的,这个函数在一个函数中从技术上来说就是闭包。 每次调用父函数时,都会创建一个新的执行上下文,其中包含所有局部变量的新副本。 这些局部变量可以在全局范围中引用,方法是将它们链接到全局声明的变量,或者从父函数返回闭包。

一个基本的例子将采取类似的格式:

function closuredFunc (){
    function closure(){
    // some logic
    }
}

也可以有一个闭包返回如下所示的几个方法:

function closure(){
    function first() { console.log('I was declared first')}
    function second() { console.log('I was declared second')}
    function third() { console.log('I was declared third')}
    return [first, second, third]
}

为了引用这些方法中的每一个,我们将把闭包分配给一个全局变量,该全局变量将指向一个公开的方法数组。 然后可以将每个方法分配给唯一的变量名,以将它们放入全局范围,如下所示。 在这一点上,他们现在可以被称为。

let f = closure()

let one = f[0]
let two = f[1]
let three = f[2]

one() // logs I was declared first
two() // logs I was declared second
three() // logs I was declared third

为什么使用闭包?

你可能想知道为什么一个人要费那么大劲做闭包。 闭包有很多用途和优点。

在 ES6中引入类之前,闭包提供了一种创建类似于面向对象编程的类隐私的方法,允许我们模拟私有方法。 这就是所谓的模块模式,它允许我们编写容易维护的代码,减少名称空间污染,提高可重用性。

让我们来看一个这样做的案例:

var makeCounter = function() {
  var privateCounter = 0;
  function changeBy(val) {
    privateCounter += val;
  }
  return {
    increment: function() {
      changeBy(1);
    },
    decrement: function() {
      changeBy(-1);
    },
    value: function() {
      return privateCounter;
    }
  }
};

var counter1 = makeCounter();
var counter2 = makeCounter();

counter1.value(); // returns 0
counter1.increment(); // adds 1
counter1.increment(); // adds 1
counter1.value(); // returns 2
counter1.decrement(); //subtracts 1
counter1.value(); // returns 1
counter2.value(); // returns 0

在上面的例子中,我们声明了一个函数 makeCounter,它是一个公共函数,可以访问其中的一些私有变量,比如 privateCounter 和操纵它的函数。 这模仿了将 makeCounter 创建为一个具有自己的功能和变量的类的行为。 当我们创建两个不同的计数器,counter1和 counter2时,就可以看出这一点。 每个计数器独立于另一个计数器,并引用不同版本的变量。

闭包还允许我们使用函数来创建其他函数,为它们的参数添加特定的值。 在这种情况下,允许这种行为的父函数称为函数工厂,因为它实际上创建了其他函数。

通过使用函数工厂,我们能够实现一种称为 Currying 的行为,我们将在下一节中介绍。

什么是咖喱

Currying 是一种可以立即计算和返回其他函数的函数模式。 这是因为 Javascript 函数是可以返回其他函数的表达式。

Curried 函数通过定义并立即返回它们的内部函数来链接闭包来构造。

下面是一个占卜的例子:

let greeting = function (a) {
    return function (b) {
        return a + ' ' + b
    }
}

let hello = greeting('Hello')
let morning = greeting('Good morning')

hello('Austin') // returns Hello Austin
hello('Roy') // returns Hello Roy
morning('Austin') // returns Good morning Austin
morning('Roy') //returns Good Morning Roy

由 greeting (hello 和 morning)创建的两个函数分别返回处理所提供输入的函数,以生成 greeting 语句。 他们还有一个参数,这个参数就是要迎接的人的名字。

在上面的例子中,问候语也被用作一个函数工厂,由它生成 hello 和 morning 两个函数。

内部函数也可以在第一次调用后调用,如下所示:


greeting('Hello There')('General Kenobi') 
//returns Hello There General Kenobi

Currying 被认为是函数式编程的一部分,因此这类 curryed 函数可以很容易地用 ES6中的箭头函数语法和新版本的 Javascript 编写,以获得更干净、更优雅的代码:


let greeting = (a) => (b) => a + ' ' + b 

greeting('Hello There')('General Kenobi') 
//returns Hello There General Kenobi

总结

尽管闭包可能不像 Javascript 在 ES6中引入类那样被普遍使用,但是在编写可重用的代码时,它们仍然占有一席之地。 闭包和局部套用也是需要理解的重要概念,当涉及到函数式编程时,它们本质上与面向对象编程中的私有方法有着相似的用途。