哇,你是不是完全糊涂了。好吧,我会尽量简单地解释闭包。
首先,我们将从范围开始。有两种类型的范围:
- 块范围
- 功能范围
块作用域在程序中出现时立即开始。另一方面,函数作用域直到函数被调用才开始。因此,对同一函数的多次调用会导致创建多个范围。
JavaScript 没有块作用域。它只有功能范围。因此,要模拟块作用域,我们需要创建一个函数表达式并立即执行它。此模式称为立即调用函数表达式 (IIFE),如下所示:
(function () {
// this is the JS equivalent of a block scope
}());
除了块作用域和函数作用域之外,还有另一种对作用域进行分类的方法。因此我们还有:
- 词法作用域
- 动态范围
这种区别仅适用于函数作用域,因为块作用域始终是词法作用域。JavaScript 只有词法范围。
要了解词法作用域和动态作用域之间的区别,我们需要了解自由变量和绑定变量之间的区别。
- 自由变量是在函数中使用但未在该函数中声明的变量。
- 在函数中声明的变量被称为绑定到该函数。
考虑以下程序:
function add(x, y) {
return x + y; // x and y are bound to add
}
在上面的程序中,变量x
和y
绑定到函数 add 因为它们是在add
.
另一方面,变量x
和y
以下程序中的变量在函数内是自由的,add
因为它们没有在函数内声明,add
但它们在函数内使用add
:
function add() {
return x + y; // x and y are free within add
}
现在自由变量是个问题。它们需要映射到某个值,但是哪个值?这就是词法作用域和动态作用域发挥作用的地方。我不会详细介绍主要细节,但您可以在 Wikipedia 上阅读相关内容。
范围很像原型继承。当一个新的作用域开始时,它从父作用域继承,形成一个作用域链,很像 JavaScript 中的原型链。
词法作用域和动态作用域在新作用域继承形式的父作用域方面有所不同。
- 在词法作用域中,新函数作用域继承自定义该函数的作用域(即其词法环境)。
- 在动态作用域中,新函数作用域继承自调用该函数的作用域(即调用作用域)。
由于 JavaScript 只有词法作用域,我们不会为动态作用域而烦恼。考虑以下程序:
var count = 0;
function incrementCount() {
return ++count;
}
(function () {
var count = 100;
alert(incrementCount()); // 1
}());
这里函数incrementCounter
有一个自由变量 - count
。由于 JavaScript 具有词法作用域count
,因此将映射到全局变量而不是IIFE 中声明count
的局部变量。count
因此incrementCount
返回1
而不是101
。
现在闭包仅适用于具有词法作用域的语言。考虑以下程序:
function getCounter() {
var count = 0;
return function () {
return ++count;
};
}
var counter = getCounter();
alert(counter()); // 1
alert(counter()); // 2
alert(counter()); // 3
在上面的程序中,返回的函数getCounter
是一个相对于变量的闭包,count
因为:
- 该变量
count
在返回的函数中是自由的(即counter
)。
- 该函数被移到声明的范围之外
count
。
这两个条件都是函数被称为闭包的必要条件。有关更多信息,请阅读以下答案:https ://stackoverflow.com/a/12931785/783743
现在要理解的重要一点是,counter
即使它可能永远不会被调用,该函数仍将被称为闭包。闭包只是一个关闭变量的函数(称为闭包的上值)。
当我们调用时,getCounter
我们创建了一个新的作用域(我们称之为这个作用域A
),每次我们调用由getCounter
(ie counter
) 返回的函数时,我们都会创建一个继承自作用域的新作用域A
。就这样。不会创建新的闭包。