JavaScript中的闭包是什么?


JavaScript闭包是一个记住创建它的环境的函数。我们可以把它想象成一个只有一个方法和私有变量的对象。JavaScript闭包是一种特殊的对象,它包含函数和函数的局部范围,以及创建闭包时的所有变量(环境)。

要理解闭包,首先我们需要理解JavaScript中的作用域。我们可以在三个层次的范围内创建变量或函数:

  1. 全球范围
  2. 函数或局部范围
  3. 词汇范围

I have written in details about scoping here,但是在进入闭包之前,让我们先简单了解一下作用域。

JavaScript中的作用域

一旦我们创建了一个变量,它就在全局范围内。因此,如果我们已经创建了一个不在任何函数内部的变量,它就在一个全局范围内。

var foo = "foo";
console.log(foo); 


如果某样东西在全球范围内,我们可以在任何地方访问它——这使它同时成为我们的朋友和敌人。将所有东西都放在全局范围内从来都不是一个好主意,因为这可能会导致名称空间冲突和其他问题。如果某个东西在全局范围内,它可以从任何地方访问,如果函数中有同名的变量,这可能会导致冲突。

任何不在全局范围内的变量或函数都在函数或局部范围内。考虑下面的列表:

function foo() { 
  var doo = "doo"; 
  console.log(doo); 
} 
foo(); 


我们创建了一个变量“doo”,它在函数“foo”的函数范围内。变量doo的生存期对于函数foo是本地的,不能在函数foo之外访问。这在JavaScript中叫做局部作用域。让我们考虑下图所示的代码:


在这里,我们已经在函数foo中创建了一个变量,与全局范围内的一个变量同名,因此现在我们有两个变量。我们要记住,这些变量是两个不同的变量,有各自的生命时间。在函数外部,值为a的变量doo是可访问的,但是在函数foo内部,值为doo的变量doo是存在的。作为参考,下面给出了上述代码:

var doo = "a"; 
function foo() { 
  var doo = "doo"; 
  console.log(doo); //print doo
} 
foo(); 
console.log(doo);//print a 


让我们稍微调整一下上面的代码片段,如下面的清单所示:

var doo = "a"; 
function foo() { 
  doo = "doo"; 
  console.log(doo);//print doo 
} 
foo(); 
console.log(doo);//print doo 


现在我们没有变量doo的两个范围。在foo函数内部,在全局范围内创建的变量doo正在被修改。我们不是在foo中重新创建变量doo,而是从全局范围修改现有的变量doo。

 

在本地或函数范围内创建变量时,我们必须使用关键字var来创建变量。否则,变量将在全局范围内创建,或者如果变量已经存在于全局范围内,它将被修改。

在JavaScript中,我们可以在函数内部有一个函数。可以有任何级别的嵌套函数,这意味着我们可以有任何数量的函数相互嵌套。考虑下面的列表:

function foo() { 
  var f = "foo"; 
  function doo() { 
    console.log(f); 
  } 
  doo(); 
} 
foo();//print foo 


本质上,在上面的代码片段中,我们有一个函数doo,它是在function foo中创建的,它没有任何自己的变量。函数foo创建了一个局部变量f,它可以在函数doo中访问。函数doo是函数foo的内部函数,它可以访问函数foo的变量。另外,函数doo可以在函数foo的体内调用。函数doo可以访问父函数中声明的变量,这是由于JavaScript的词法范围。

这里有两个层次的范围界定:

  1. 父函数foo范围
  2. 儿童功能斗镜


由于JavaScript的词法范围,在函数doo中创建的变量可以访问在函数foo范围内创建的所有内容。但是,function foo不能访问function doo的变量。


JavaScript中的闭包

让我们从一个例子开始理解JavaScript中的闭包。考虑如下所示的代码。我们不是在函数foo的体内调用函数doo,而是从函数foo返回函数doo。

function foo() { 
   var f = "foo"; 
   function doo() { 
     console.log(f); 
   } 
   return doo; 
 } 
var afunct = foo(); 
afunct(); 

 

在上面的代码中:

  1. 函数foo正在返回另一个函数doo
  2. 函数doo没有任何自己的变量;
  3. 由于词法范围,函数doo能够访问父函数foo的变量;
  4. 函数foo被调用并赋给变量afunct
  5. 然后作为函数调用afunct,并打印字符串“foo”

 

令人惊讶的是,上述代码片段的输出是字符串“foo”。现在我们可能会感到困惑——变量f是如何在函数foo之外被访问的?通常,函数中的局部变量只在函数执行期间存在。因此,理想情况下,在执行foo之后,变量f应该不再是可访问的。但是在JavaScript中,我们可以访问它,因为afunct已经变成了JavaScript闭包。闭包afunct在afunct闭包创建时拥有关于函数doo和函数doo的所有局部范围变量的信息。

 

在闭包的情况下,内部函数保留外部函数范围的引用。所以,在闭包中:

  • 内部函数保持对其外部函数范围的引用。在这种情况下,函数doo保持对函数foo范围的引用。
  • Function doo可以在任何时候从function foo作用域引用中访问变量,即使外部function foo已经完成执行。
  • JavaScript将外部函数(在本例中为foo)的范围引用及其变量保存在内存中,直到内部函数存在并引用它。在这种情况下,函数foo的作用域和变量将由JavaScript保存在内存中,直到函数doo存在。

为了更好地理解闭包,让我们再讨论一个例子:

function add(num1) { 
   function addintern(num2) { 
     return num1 + num2; 
   } return addintern; 
} 
var sum9 = add(7)(2);
console.log(sum9); 
var sum99 = add(77)(22); 
console.log(sum99); 


我们这里有两个闭包,sum9和sum99。

当创建sum9闭包时,在函数addintern的局部范围内,num1的值是7,JavaScript在创建sum9闭包时会记住这个值。

关闭sum99时也是如此...在函数addintern的局部范围内,num1的值是7,JavaScript在创建闭包sum99时记住了这个值。正如预期的那样,产出将是9和99。

我们可以把闭包想象成一个带有私有变量和一个方法的对象。闭包允许我们用对数据起作用的函数来附加数据。因此,闭包可以定义为具有以下特征:

  • 它是一个物体
  • 它包含一个函数
  • 它记住与函数相关的数据,包括创建闭包时函数局部范围的变量
  • 要创建闭包,函数应该返回另一个函数引用

最后,我们可以定义一个闭包:

“JavaScript闭包是一种特殊的对象,它包含一个函数和创建该函数的环境。这里,环境代表函数的局部范围及其在闭包创建时的所有变量。”

结论

在这篇文章中,我们学习了JavaScript中的闭包。希望你觉得有用。感谢阅读!