简介
如果您用JavaScript编写代码,很可能会遇到术语闭包
,这是一个有用但常常令人困惑的概念。但结束到底是什么呢?
闭包可以描述为函数和声明它时所处的词汇环境的组合。
但这到底意味着什么呢?创建函数时,词法环境 由函数作用域中的所有局部变量组成。闭包使人们能够引用函数的所有局部变量,这些变量处于找到它们的状态。
这基本上是通过在另一个函数中定义一个函数来实现的,一个函数中的这个函数从技术上讲就是闭包。每次调用父函数时,都会创建一个新的执行上下文,其中包含所有局部变量的最新副本。通过将这些局部变量链接到全局声明的变量或从父函数返回闭包,可以在全局范围内引用这些局部变量。
一个基本的示例将采用类似如下的格式:
1function closuredFunc (){
2 function closure(){
3 // some logic
4 }
5}
也可以有一个返回几个方法的闭包,如下所示:
1function closure(){
2 function first() { console.log('I was declared first')}
3 function second() { console.log('I was declared second')}
4 function third() { console.log('I was declared third')}
5 return [first, second, third]
6}
要引用这些方法中的每一个,我们将把闭包分配给一个全局变量,然后该变量将指向一个公开的方法数组。然后,可以为每个方法分配唯一的变量名,以使它们进入全局作用域,如下所示。在这一点上,他们现在可以调用。
1let f = closure()
2
3let one = f[0]
4let two = f[1]
5let three = f[2]
6
7one() // logs I was declared first
8two() // logs I was declared second
9three() // logs I was declared third
为什么使用闭包?
你可能想知道为什么一个人会经历关闭的麻烦。闭包有很多用途和优点。
在ES6中引入类之前,闭包提供了一种创建类隐私的方法,类似于面向对象编程中使用的方法,允许我们模拟私有方法。这就是所谓的‘模块模式’,它允许我们编写易于维护的代码,减少命名空间污染,提高可重用性。
让我们来看一个这样做的案例:
1var makeCounter = function() {
2 var privateCounter = 0;
3 function changeBy(val) {
4 privateCounter += val;
5 }
6 return {
7 increment: function() {
8 changeBy(1);
9 },
10 decrement: function() {
11 changeBy(-1);
12 },
13 value: function() {
14 return privateCounter;
15 }
16 }
17};
18
19var counter1 = makeCounter();
20var counter2 = makeCounter();
21
22counter1.value(); // returns 0
23counter1.increment(); // adds 1
24counter1.increment(); // adds 1
25counter1.value(); // returns 2
26counter1.decrement(); //subtracts 1
27counter1.value(); // returns 1
28counter2.value(); // returns 0
在上面的例子中,我们已经声明了一个函数makeCounter
,它是一个公共函数,可以访问其中的一些私有变量,例如privateCounter
和操纵它的函数。这模仿了将makeCounter创建为一个类的行为,它有自己的内置功能和变量。当我们创建两个不同的计数器时,可以看到这一点,counter1
和counter2
。每个计数器彼此独立,并引用不同版本的变量。
闭包还允许我们使用函数来创建其他函数,这些函数将特定值添加到其参数中。在这种情况下,允许这种行为的父函数被称为函数工厂
,因为它本质上创建了其他函数。
使用函数工厂,我们能够实现一种称为Currying
的行为,我们将在下一节中介绍。
什么叫咖喱
Currying
是函数立即求值并返回其他函数的模式。这是因为Java脚本函数是可以返回其他函数的表达式。
Curry函数是通过同时定义并立即返回闭包的内部函数来链接闭包来构造的。
下面是一个讨好的例子:
1let greeting = function (a) {
2 return function (b) {
3 return a + ' ' + b
4 }
5}
6
7let hello = greeting('Hello')
8let morning = greeting('Good morning')
9
10hello('Austin') // returns Hello Austin
11hello('Roy') // returns Hello Roy
12morning('Austin') // returns Good morning Austin
13morning('Roy') //returns Good Morning Roy
从greeting
(hello
和morning
)创建的两个函数都返回处理提供的输入的函数,以生成问候语。他们还接受一个论点,那就是被问候人的名字。
在上面的例子中,问候语也被用作一个函数工厂,由它生成两个函数Hello和Morning。
内部函数也可以在第一次调用后调用,如下所示:
1greeting('Hello There')('General Kenobi')
2//returns Hello There General Kenobi
Currying被认为是函数式编程的一部分,因此curryed函数可以很容易地在ES6和较新版本的JavaScript中使用箭头函数语法编写,以获得更清晰,更优雅的代码:
1let greeting = (a) => (b) => a + ' ' + b
2
3greeting('Hello There')('General Kenobi')
4//returns Hello There General Kenobi
总结
虽然闭包可能不会像在ES6中使用的类那样常用,但在编写干净的可重用代码方面,闭包仍然占有一席之地。当涉及到函数式编程时,闭包和汇流也是需要理解的重要概念,在面向对象编程中,它们本质上具有类似于私有方法的用途。