一个示例一行行代码理解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 中一开始难以掌握的微妙概念之一。但是一旦你理解了它们,你就会意识到事情并没有那么复杂难懂。
相关文章
标签: 闭包
- 站长推荐