什么是闭包
维基百科中的概念
- 在计算机科学中,闭包(英语:Closure),又称词法闭包(Lexical Closure)或函数闭包(function closures),是在支持头等函数的编程语言中实现词法绑定的一种技术。闭包在实现上是一个结构体,它存储了一个函数(通常是其入口地址)和一个关联的环境(相当于一个符号查找表)。环境里是若干对符号和值的对应关系,它既要包括约束变量(该函数内部绑定的符号),也要包括自由变量(在函数外部定义但在函数内被引用),有些函数也可能没有自由变量。闭包跟函数最大的不同在于,当捕捉闭包的时候,它的自由变量会在捕捉时被确定,这样即便脱离了捕捉时的上下文,它也能照常运行。捕捉时对于值的处理可以是值拷贝,也可以是名称引用,这通常由语言设计者决定,也可能由用户自行指定。
- 闭包和匿名函数经常被用作同义词。但严格来说,匿名函数就是字面意义上没有被赋予名称的函数,而闭包则实际上是一个函数的实例,也就是说它是存在于内存里的某个结构体。如果从实现上来看的话,匿名函数如果没有捕捉自由变量,那么它其实可以被实现为一个函数指针,或者直接内联到调用点,如果它捕捉了自由变量那么它将是一个闭包;而闭包则意味着同时包括函数指针和环境两个关键元素。在编译优化当中,没有捕捉自由变量的闭包可以被优化成普通函数,这样就无需分配闭包结构体,这种编译技巧被称为函数跃升。
闭包和状态表达
- 闭包可以用来在一个函数与一组“私有”变量之间创建关联关系。在给定函数被多次调用的过程中,这些私有变量能够保持其持久性。变量的作用域仅限于包含它们的函数,因此无法从其它程序代码部分进行访问。不过,变量的生存期是可以很长,在一次函数调用期间所创建所生成的值在下次函数调用时仍然存在。正因为这一特点,闭包可以用来完成信息隐藏,进而应用于需要状态表达的某些编程范型中。
学术上
- 闭包在 JavaScript 中是指,内部函数总是可以访问其所在的外部函数中声明的参数和变量,即使在其外部函数被返回return掉(寿命终结)了之后。
个人理解
- 闭包是在函数里面定义一个函数,该函数可以是匿名函数,子函数能够读写父函数的局部变量
Understanding the JavaScript Closures
In the JavaScript functions chapter you've learn that in JavaScript a variable's scope can be global or local. Since ES6 you can also create block-scoped variables using the
let
keyword.在JavaScript函数一章中,您已经了解到,在JavaScript中,变量的作用域可以是全局或局部的。
A global variable can be accessed and manipulated anywhere in the program, whereas a local variable can only be accessed and manipulated by the function they are declared in.
全局变量可以在程序中的任何位置访问和操作,而局部变量只能由声明它们的函数访问和操作。
However, there are certain situations when you want a variable to be available throughout the script, but you don't want just any part of your code to be able to change its value accidentally.
然而,在某些情况下,您希望变量在整个脚本中都可用,但是您不希望代码的任何部分都能意外更改其值。
Let's see what happens if you try to achieve this using the global variable:
让我们看看如果您尝试使用全局变量来实现此目标,将会发生什么:
// Global variable
var counter = 0;
// A function dedicated to manipulate the 'counter' variable
// 专门用于操纵'counter'变量的函数
function makeCounter() {
return counter += 1;
}
// Calling the function
makeCounter();
console.log(counter); // Prints: 1
makeCounter();
console.log(counter); // Prints: 2
// Trying to manipulate the 'counter' variable from outside
// 试图从外部操纵'counter'变量
counter = 10;
console.log(counter); // Prints: 10
As you can see in the above example, the value of the
counter
variable can be changed from anywhere in the program, without calling themakeCounter()
function如您在上面的示例中看到的那样,可以在程序中的任何位置更改计数器变量的值,而无需调用makeCounter()函数
Now, let's try to achieve the same thing with the local variable and see what happens
现在,让我们尝试使用局部变量来实现相同的目的,然后看看会发生什么
function makeCounter() {
// Local variable
var counter = 0;
// Manipulating the 'counter' variable
return counter += 1;
}
// Calling the function
console.log(makeCounter()); // Prints: 1
console.log(makeCounter()); // Prints: 1
In this case the
counter
variable cannot be manipulated from outside, since it is local tomakeCounter()
function, but its value will also not increase after subsequent function call, because every time we call the function it reset thecounter
variable value, which you can clearly see in the above example (line no-11). The JavaScript closure can solve our problem.在这种情况下,无法从外部操作计数器变量,因为它是makeCounter()函数的本地变量,但在后续函数调用后其值也不会增加,因为每次调用该函数都会重置计数器变量值,您可以可以在上面的示例(第11行)中清楚地看到。JavaScript闭包可以解决我们的问题。
A closure is basically an inner function that has access to the parent function's scope, even after the parent function has finished executing. This is accomplished by creating a function inside another function. Let's take a look at the following example to see how it works:
闭包基本上是一个内部函数,即使父函数执行完毕,它也可以访问父函数的作用域。这是通过在另一个函数内部创建一个函数来实现的。让我们看下面的示例,看看它是如何工作的:
function makeCounter() {
var counter = 0;
// Nested function
function make() {
counter += 1;
return counter;
}
return make;
}
/* Execute the makeCounter() function and store the
returned value in the myCounter variable */
// 执行makeCounter()函数并存储myCounter变量中返回的值
var myCounter = makeCounter();
console.log(myCounter()); // Prints: 1
console.log(myCounter()); // Prints: 2
As you can see in the above example, the inner function
make()
is returned from the outer functionmakeCounter()
. So the value of themyCounter
is the innermake()
function (line no-14), and callingmyCounter
effectively callsmake()
. In JavaScript functions can assigned to variables, passed as arguments to other functions, can be nested inside other functions, and more.如上例所示,内部函数make()是从外部函数makeCounter()返回的。因此,myCounter的值是内部的make()函数(第14行),调用myCounter有效地调用make()。在JavaScript中,可以将函数分配给变量,将其作为参数传递给其他函数,也可以嵌套在其他函数中,等等。
You'll also notice that the inner function
make()
is still able to access the value ofcounter
variable defined in the outer function, even though themakeCounter()
function has already finished executing (line no-14). It happens because functions in JavaScript form closures. Closures internally store references to their outer variables, and can access and update their values.您还会注意到,即使makeCounter()函数已经完成执行(第14行),内部函数make()仍然能够访问在外部函数中定义的counter变量的值。发生这种情况是因为JavaScript中的函数形成了闭包。闭包内部存储对其外部变量的引用,并且可以访问和更新其值。
In the example above, the
make()
function is a closure whose code refers to the outer variablecounter
. This implies that whenever themake()
function is invoked, the code inside it is able to access and update thecounter
variable because it is stored in the closure.在上面的示例中,make()函数是一个闭包,其代码引用了外部变量counter。这意味着无论何时调用make()函数,由于其存储在闭包中,因此其内部的代码能够访问和更新counter变量。
Finally, since the outer function has finished executing, no other part of the code can access or manipulate the
counter
variable. Only the inner function has exclusive access to it.最后,由于外部函数已完成执行,因此代码的其他部分都无法访问或操纵counter变量。仅内部函数对其具有独占访问权。
The previous example can also be written using anonymous function expression, like this:
前面的示例也可以使用匿名函数表达式编写,如下所示:
// Anonymous function expression
var myCounter = (function() {
var counter = 0;
// Nested anonymous function
return function() {
counter += 1;
return counter;
}
})();
console.log(myCounter()); // Prints: 1
console.log(myCounter()); // Prints: 2
Tip: In JavaScript, all functions have access to the global scope, as well as the scope above them. As JavaScript supports nested functions, this typically means that the nested functions have access to any value declared in a higher scope including its parent function's scope.
提示:在JavaScript中,所有函数都可以访问全局范围以及它们上方的范围。由于JavaScript支持嵌套函数,因此通常意味着嵌套函数可以访问在较高范围(包括其父函数的范围)中声明的任何值。
Note: The global variables live as long as your application (i.e. your web page) lives. Whereas, the local variables have a short life span, they are created when the function is invoked, and destroyed as soon as the function is finished executing.
注意:全局变量的寿命与您的应用程序(即您的网页)的寿命一样长。局部变量的寿命很短,它们是在调用函数时创建的,并在函数执行完毕后立即销毁。
Creating the Getter and Setter Functions
Here we will create a variable
secret
and protect it from being directly manipulated from outside code using closure. We will also create getter and setter functions to get and set its value.在这里,我们将创建一个变量secret,并防止它使用闭包直接从外部代码中进行操作。我们还将创建getter和setter函数以获取并设置其值。
Additionally, the setter function will also perform a quick check whether the specified value is a number or not, and if it is not it will not change the variable value.
另外,setter函数还将快速检查指定的值是否为数字,如果不是,则不会更改变量值。
var getValue, setValue;
// Self-executing function
(function() {
var secret = 0;
// Getter function
getValue = function() {
return secret;
};
// Setter function
setValue = function(x) {
if(typeof x === "number") {
secret = x;
}
};
}());
// Calling the functions
console.log(getValue()); // Returns: 0
setValue(10);
console.log(getValue()); // Returns: 10
setValue(null);
console.log(getValue()); // Returns: 10
Tip: Self-executing functions are also called immediately invoked function expression (IIFE), immediately executed function, or self-executing anonymous function.
提示:自执行函数也称为立即调用函数表达式(IIFE),立即执行的函数或自我执行的匿名函数。
Lexical environment
let makeIncrement, makeDecrease;
function make() {
let count;
makeIncrement = function() {
return ++count;
};
makeDecrease = function() {
return --count;
}
count = 1;
console.log(`inside make, call to makeIncrement(): ${makeIncrement()}`);
}
make(); // 2
console.log(`call to makeDecrease(): ${makeDecrease()}`); // 1 (--count)
console.log(`call to makeDecrease(): ${makeDecrease()}`); // 0 (--count)
console.log(`call to makeIncrement(): ${makeIncrement()}`); // 1 (++count)
console.log(`call to makeIncrement(): ${makeIncrement()}`); // 2 (++count)
Reference to an unbound variable
// Reference to an unbound variable
let moduleSet = {
x: 42,
getX: function() {
return this.x;
}
};
let unboundGetX= moduleSet.getX;
console.log(unboundGetX());
// This function gets invoked at the gloal scope
// emits undefined as 'x' is not specified in global scope
// 函数在全局范围内被调用
// 发出undefined,因为未在全局范围内指定'x'
// specify object moduleSet as the closure
// 指定对象moduleSet为闭包
let boundGetX = unboundGetX.bind(moduleSet);
console.log(boundGetX()); // emits 42