理解 JavaScript 闭包
这确实是一个很难理解的概念,我花了一段时间才理解闭包是什么。以下是我理解的闭包,并附上一些图表和代码片段,以便更好地理解。
为了热身,我将从一个不涉及闭包的示例开始,然后慢慢将我的示例更改为最后涉及闭包
function add1(){
var x = 1;
var f = function(y){
return x + y;
}
return f(3);
}
console.log(add1());
这里我们有一个简单的函数叫做add1
。
- 它有一个局部变量
x
,其值为1
- 另一个
f
被赋予函数的变量 - 此
add1
函数返回函数f
因此,如果我们运行此代码,它将返回4
= x
1 和y
= 3
由于我们有一个函数声明,并且该函数在 console.log 中被调用。执行上下文如下:
首先,我们将进入global execution context
创建阶段。我们将进入creation
andexecution
阶段。在这个creation
阶段,我们将add1
函数提升到作用域的顶部;在这个execution
阶段,我们将执行add1
函数。
在这个执行上下文中,我们将在阶段提升变量x
和 ,并且在 阶段它们都是未定义的。在阶段,被赋值,被赋值给接受参数 的函数。赋值后,它返回 函数调用;f
creation
creation
execution
x
1
f
y
f(3)
这会创建另一个名为 f 的执行上下文。在f
执行上下文中,由于没有声明任何变量或函数,因此在创建阶段我们不会提升任何内容。在执行阶段,它会返回,x+y
其中 的值x
是通过执行上下文中的作用域链获取的add1
,而 的值y
是通过以下方式传入的:f(3)
因此总体x
而言是1
且y
是3
。返回4
。每个 EC 执行完成后,都会将其逐一弹出堆栈。
在执行上下文中f
,它返回的是x+y
。 的值y
在此上下文中是可获取的,即 的值3
,但 的值x
不是。因此,它引用外部执行上下文并检索 的值,x
即1
。
呼……真是太多了。希望你能够理解执行上下文和作用域。现在我们准备继续讨论一些更复杂的东西,它与闭包有关。以下是代码片段
var add = function() {
var x = 1;
var f = function(y) {
return x + y;
};
return f;
};
var g = add();
console.log(g(3));
add
这个例子与第一个不同,我们现在返回的是函数的引用而不是值。然后,我们在函数调用时在函数作用域之外调用了该函数g(3)
。这部分可能比较难理解。
让我们再回顾一下执行上下文,看看它是如何运作的。首先是全局执行上下文
接下来是add
执行上下文
现在到了最有趣的部分。一旦add
执行上下文完成执行,它将被弹出堆栈。
接下来是 g(3) 执行上下文。记住,该变量g
包含函数f
,因为它是 function 中返回的函数add
。
所以g
会返回x + y
。我们正在运行g(3)
所以的值y
将是 3。
的值呢x
?
好吧,如果我们四处寻找,我们将无法找到x
。但我们记得在另一个已创建、执行并弹出的执行上下文中,它x
被设置为。1
因此Javascript
,如果一个函数在另一个函数(例如)内部创建g
,那么g
它将保留对封闭函数范围内变量的引用(在本例中)add
。这意味着g
仍然可以访问add
包含变量的执行上下文的内存x
。
基本上,add
执行上下文已经结束,但g
仍然允许访问仍在执行上下文内存中的执行上下文的变量add
。
尽管它不再位于执行堆栈中,我们的函数g
仍然可以沿着作用域链向上爬并找到它。x
g's
执行上下文有closed in
x
一个 ,即使x's
执行上下文消失了,它仍然是一个外部变量。所以我们的函数g
是一个closure
。
像函数这样的闭包g
在内部存储对外部变量的引用。
我们有全局执行上下文范围。我们将拥有add
和g
变量以及它们的执行上下文。
行var g = add();
完成后,添加执行上下文将从堆栈中弹出。(由于这个原因,它被阴影化了)
然后我们创建并执行执行上下文。即使执行上下文不再位于堆栈中,g
它仍然可以引用外部变量。x
x's
希望你通过本文对闭包有了一定的了解。如果你仍然感到困惑,请阅读以下参考资料。
这是我用来进一步理解这个主题的主要参考资料
执行上下文的终极指南