一个示例一行行代码理解JS闭包是如何执行的
作者:admin 时间:2022-6-7 17:19:34 浏览:JavaScript 中的闭包是许多人难以理解的概念之一。在接下来的文章中,我将清楚地解释什么是闭包,并且我将使用简单的代码示例来说明这一点。
什么是闭包?
闭包是 JavaScript 中的一项功能,其中内部函数可以访问外部(封闭)函数的变量——作用域链。
闭包具有三个作用域链:
- 它可以访问自己的范围——在大括号之间定义的变量
- 它可以访问外部函数的变量
- 它可以访问全局变量
对于新手来说,这个定义似乎不好理解。不过没关系,下面会通过简单的示例说明,新手也可很快理解它。
真正的闭包是什么?
让我们看一个 JavaScript 中的简单闭包示例:
function outer() {
var b = 10;
function inner() {
var a = 20;
console.log(a+b);
}
return inner;
}这里我们有两个函数:
outer()是具有变量b的外部函数,并返回inner函数。inner()是一个内部函数,它的变量a被调用,并在其函数体内访问outer()的一个变量b。
变量b的作用域仅限于outer函数,变量a的作用域仅限于inner函数。
现在让我们调用outer()函数,并将结果存储在一个变量X中。然后我们再次调用outer()函数并将其存储在变量Y中。
function outer() {
var b = 10;
function inner() {
var a = 20;
console.log(a+b);
}
return inner;
}
var X = outer(); //outer() 第一次调用
var Y = outer(); //outer() 第二次调用让我们一步一步地看看outer()函数第一次被调用时会发生什么:
- 变量
b已创建,其范围仅限于outer()函数,其值设置为10。 - 下一行是一个函数声明,所以没有什么要执行的。
- 在最后一行,
return inner查找名为inner的变量,发现该变量inner实际上是一个函数,因此返回整个函数体inner。 - [请注意,该
return语句不执行内部函数, 一个函数仅在后跟()时执行,而是该return语句返回函数的整个主体。] return语句返回的内容存储在X中,因此,X将存储以下内容:
function inner() {
var a=20;
console.log(a+b);
}- 函数
outer()执行完毕,现在outer()范围内的所有变量都不存在了。
最后一部分很重要,需要理解。一旦函数完成执行,在函数范围内定义的任何变量都将不复存在。
在函数内部定义的变量的生命周期就是函数执行的生命周期。
这意味着在console.log(a+b)中,变量b仅在outer()函数执行期间存在。一旦outer函数完成执行,变量b就不再存在。
当函数第二次执行时,函数的变量会被再次创建,直到函数完成执行。
因此,当outer()第二次调用时:
- 创建了一个新变量
b,其范围仅限于outer()函数,其值设置为10。 - 下一行是一个函数声明,所以没有什么要执行的。
return inner返回整个函数体inner。return语句返回的内容存储在Y中。- 函数
outer()执行完毕,现在outer()范围内的所有变量都不存在了。
这里重要的一点是,当outer()第二次调用函数时,b会重新创建变量。此外,当outer()函数第二次完成执行时,这个新变量b再次不复存在。
这是要实现的最重要的一点。函数内部的变量只有在函数运行时才存在,一旦函数执行完毕就不再存在。
现在,让我们回到我们的代码示例,看看X和Y。由于outer()函数在执行时返回一个函数,因此变量X和Y是函数。
这可以通过在 JavaScript 代码中添加以下内容来轻松验证:
console.log(typeof(X)); //X 是类型函数
console.log(typeof(Y)); //Y 是类型函数
由于变量X和Y是函数,我们可以执行它们。在 JavaScript 中,可以通过()在函数名称后添加来执行函数,例如X()和Y()。
function outer() {
var b = 10;
function inner() {
var a = 20;
console.log(a+b);
}
return inner;
}
var X = outer();
var Y = outer();
// outer()函数执行完毕
X(); // X() 第一次调用
X(); // X() 第二次调用
X(); // X() 第三次调用
Y(); // Y() 第一次调用当我们执行X()和Y()时,我们本质上是在执行inner函数。
让我们逐步检查X()第一次执行时会发生什么:
- 创建了变量
a,并将其值设置为20。 - JavaScript 现在尝试执行
a + b,JavaScript 知道a的存在,因为它刚刚创建它。但是,变量b不再存在。由于b是外部函数的一部分,b因此仅在outer()函数执行时存在。由于outer()函数在我们调用X()之前就完成了执行,因此outer函数范围内的任何变量都不再存在,因此变量b也不再存在。
由于 JavaScript 中的闭包,该inner函数可以访问封闭函数的变量。换句话说,inner函数在执行封闭函数时保留封闭函数的作用域链,因此可以访问封闭函数的变量。
在我们的示例中,inner函数保存了outer()函数执行b=10时的值,并继续保存(关闭)它。
它现在引用它的作用域链,并注意到b在其作用域链中确实具有变量的值,因为它在outer函数执行b时将值封闭在闭包中。
因此,JavaScript 知道a=20和b=10,并且可以计算a+b。
你可以通过在上面的示例中添加以下代码行来验证这一点:
function outer() {
var b = 10;
function inner() {
var a = 20;
console.log(a+b);
}
return inner;
}
var X = outer();
console.dir(X); //使用 console.dir() 代替 console.log()在控制台,你可以展开元素以实际查看闭包元素(如下面倒数第四行所示)。请注意,即使在outer()函数完成执行后, 闭包的值b=10也会保留。

变量 b=10 保存在闭包中
现在让我们重新回顾一下我们在开始时看到的闭包的定义,看看它现在是否更有意义。
所以内部函数有三个作用域链:
- 访问自己的范围——变量
a - 访问
outer函数的变量——变量b - 访问可能定义的任何全局变量
进一步了解闭包
为了深入了解闭包,让我们通过添加三行代码来扩充示例:
function outer() {
var b = 10;
var c = 100;
function inner() {
var a = 20;
console.log("a= " + a + " b= " + b);
a++;
b++;
}
return inner;
}
var X = outer(); // outer() 第一次被调用
var Y = outer(); // outer() 第二次被调用
//outer()函数执行完毕
X(); // X() 第一次调用
X(); // X() 第二次调用
X(); // X() 第三次调用
Y(); // Y() 第一次调用输出
a=20 b=10
a=20 b=11
a=20 b=12
a=20 b=10
让我们一步一步地检查这段代码,看看到底发生了什么,看看闭包的实际效果!
var X = outer(); // outer()第一次调用
outer()第一次调用,执行以下步骤:
- 创建变量
b,并设置为10;
创建变量c,并设置为100。
我们在引用中调用b(第一次)和c(第一次)。 - 此时返回
inner函数并赋给X,变量b作为闭包以b=10包含在inner函数作用域链中,因为inner使用了变量b。 outer函数完成执行,其所有变量不再存在。变量c不再存在,尽管变量b作为闭包存在于inner中。
var Y= outer(); // outer()第二次调用
- 重新创建变量
b,并设置为10;
重新创建变量c,并设置为100;
请注意,即使变量之前执行过一次并且不再存在,一旦函数完成执行,它们就会被创建为全新的变量。
我们调用b(第二次)和c(第二次)作为我们的引用。 - 此时返回
inner函数并赋给Y,变量b作为闭包以b(第二次)=10包含在inner函数作用域链中,因为inner使用了变量b。 outer函数完成执行,其所有变量不再存在。
变量c(第二次)不再存在,尽管变量b(第二次)作为闭包存在于inner中。
现在让我们看看执行以下代码行时会发生什么:
X(); // X() 第一次调用
X(); // X() 第二次调用
X(); // X() 第三次调用
Y(); // Y() 第一次调用
X()第一次调用时,
- 变量
a被创建,并设置为20。 a的值=20,b的值来自闭包值,b(第一次), 所以b=10。- 变量
a和b都递增1。 X()完成执行,其所有内部变量(变量a)不再存在。
但是,b(第一次)被保存为闭包,所以b(第一次)继续存在。
X()第二次调用时,
- 变量
a被重新创建,并设置为20。 - 变量
a任何先前的值不再存在,因为它在X()第一次完成执行时不再存在。 a的值=20;b的值取自闭包值b(第一次),还要注意,我们在上一次执行中增加了b的值,所以b=11。- 变量
a和b再次递增1。 X()完成执行并且它的所有内部变量(变量a) 不再存在。
但是,b(第一次)随着闭包继续存在而被保留。
X()第三次调用时,
- 变量
a被重新创建,并设置为20;
变量a任何先前的值不再存在,因为它在X()第二次完成执行时不再存在。 b的值来自闭包值——b(第一次);
还要注意我们在之前的执行中为b的值增加了1, 所以b=12。- 变量
a和b再次递增1。 X()完成执行,其所有内部变量 (变量a)不再存在。
但是,b(第一次)随着闭包继续存在而被保留。
第一次调用 Y() 时,
- 变量
a被重新创建,并设置为20;
a的值=20,b的值来自闭包值——b(第二次),所以b=10。 - 变量
a和b均递增1。 Y()完成执行,它的所有内部变量(变量a)不再存在。
但是,b(第二次)被保存为闭包,所以b(第二次)继续存在。
结束语
闭包是 JavaScript 中一开始难以掌握的微妙概念之一。但是一旦你理解了它们,你就会意识到事情并没有那么复杂难懂。
相关文章



