技术频道导航
HTML/CSS
.NET技术
IIS技术
PHP技术
Js/JQuery
Photoshop
Fireworks
服务器技术
操作系统
网站运营

赞助商

分类目录

赞助商

最新文章

搜索

一个示例一行行代码理解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再次不复存在。

这是要实现的最重要的一点。函数内部的变量只有在函数运行时才存在,一旦函数执行完毕就不再存在。

现在,让我们回到我们的代码示例,看看XY。由于outer()函数在执行时返回一个函数,因此变量XY是函数。

这可以通过在 JavaScript 代码中添加以下内容来轻松验证:

console.log(typeof(X)); //X 是类型函数
console.log(typeof(Y)); //Y 是类型函数

由于变量XY是函数,我们可以执行它们。在 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() 第一次调用

demodownload

当我们执行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=20b=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 保存在闭包中
变量 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() 第一次调用

demodownload

输出

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
  • 变量ab都递增1。
  • X()完成执行,其所有内部变量(变量a)不再存在。
    但是,b(第一次)被保存为闭包,所以b(第一次)继续存在。

X()第二次调用时,

  • 变量a被重新创建,并设置为20。
  • 变量a任何先前的值不再存在,因为它在X()第一次完成执行时不再存在。
  • a的值=20;b的值取自闭包值b(第一次),还要注意,我们在上一次执行中增加了b的值,所以b=11
  • 变量ab再次递增1。
  • X()完成执行并且它的所有内部变量(变量 a ) 不再存在。
    但是,b(第一次)随着闭包继续存在而被保留。

X()第三次调用时,

  • 变量a被重新创建,并设置为20;
    变量a任何先前的值不再存在,因为它在X()第二次完成执行时不再存在。
  • b的值来自闭包值——b(第一次)
    还要注意我们在之前的执行中为b的值增加了1, 所以b=12
  • 变量ab再次递增1。
  • X()完成执行,其所有内部变量 (变量a)不再存在。
    但是,b(第一次)随着闭包继续存在而被保留。

第一次调用 Y() 时,

  • 变量a被重新创建,并设置为20;
    a的值=20, b的值来自闭包值—— b(第二次),所以b=10
  • 变量ab均递增1。
  • Y()完成执行,它的所有内部变量(变量a)不再存在。
    但是,b(第二次)被保存为闭包,所以b(第二次)继续存在。

结束语

闭包是 JavaScript 中一开始难以掌握的微妙概念之一。但是一旦你理解了它们,你就会意识到事情并没有那么复杂难懂。

相关文章

标签: 闭包  
x
x
  • 站长推荐