Javascript 简明教程

JavaScript - Closures

What is Closure?

JavaScript 中的 closures 的概念允许嵌套函数访问在父函数的作用域中定义的变量,即使父函数的执行已完成。简而言之,你可以使用闭包使全局变量成为局部变量或私有变量。

JavaScript closure 基本上是函数及其 lexical environment 的组合。这允许内部函数访问外部函数的作用域。每次在函数创建时创建一个闭包。

在开始学习闭包的概念之前,你需要了解词法作用域、嵌套函数和返回函数的概念。

Lexical Scoping

在 JavaScript 中,词法作用域是一个概念,其中变量的作用域在代码编译时根据代码的结构确定。例如,嵌套函数可以从父函数的作用域中访问变量,这称为词法作用域。

Nested Function

你可以在函数内部定义函数,并且内部函数称为嵌套函数。让我们通过下面的示例来了解它。

Example

在下面的示例中,我们在 outer() 函数内定义了 inner() 函数。此外,inner() 函数在 outer() 函数内执行。

当我们执行 outer() 函数时,它还执行嵌套函数 inner() 函数。

<html>
<body>
   <p id = "demo"> </p>
   <script>
      const output = document.getElementById("demo");
      function outer() {
         output.innerHTML += "The outer function is executed! <br>";
         function inner() {
            output.innerHTML += "The inner function is executed! <br>";
         }
         inner();
      }
      outer();
   </script>
</body>
</html>
The outer function is executed!
The inner function is executed!

Returning Function

当任何函数返回函数而不是值或变量时,它称为返回函数。我们来看看下面的例子。

Example

在下面的代码中,outer() 函数返回函数定义,我们将其存储在“func”变量中。之后,我们使用“func”变量来调用存储在其中的函数。

<html>
<head>
   <title> JavaScript - Returning function </title>
</head>
<body>
   <p id = "demo"> </p>
   <script>
      const output = document.getElementById("demo");
      function outer() {
         output.innerHTML += "The outer function is executed! <br>";
         return function inner() {
            output.innerHTML += "The inner function is executed! <br>";
         }
      }
      const func = outer();
      func();
      func();
   </script>
</body>
</html>
The outer function is executed!
The inner function is executed!
The inner function is executed!

现在,你已经学习了学习闭包所需的先决条件。

JavaScript 闭包的定义有点令人困惑,但我们将逐步学习闭包的概念,以便你能够清楚地理解它。

A Counter Dilemma

例如,你创建计数器来对变量进行递增和递减。如下所示,你需要使用全局变量作为计数器。

Example

在下面的示例中,“cnt”这是一个全局变量,它被初始化为 100。每当执行 decrement() 函数时,它都会将“cnt”变量的值减少 1。

<html>
<body>
   <p id = "demo"> </p>
   <script>
      const output = document.getElementById("demo");
      var cnt = 100;
      function decrement() {
         cnt = cnt - 1;
         output.innerHTML += "The value of the cnt is: " + cnt + "<br>";
      }
      decrement();
      decrement();
      decrement();
   </script>
</body>
</html>
The value of the cnt is: 99
The value of the cnt is: 98
The value of the cnt is: 97

上述代码完全可以作为递减计数器,但问题是“cnt”变量可以在代码的任何位置访问,并且代码的任何部分都可以更改它,而不执行 decrement() 函数。

这里,JavaScript 闭包就出现了。

Example: JavaScript Closures

在下面的示例中,counter() 函数返回 decrement() 函数。“cnt”变量是在 counter() 函数内部定义的,而不是在全局作用域内定义的。

decrement() 函数将“cnt”的值减少 1,并将其输出打印出来。

“func”变量包含 decrement() 函数表达式。每当你执行 func() 时,它就会调用 decrement() 函数。

<html>
<body>
   <p id = "demo"> </p>
   <script>
      const output = document.getElementById("demo");
      function counter() {
         let cnt = 100; // Works as a global variable for the decrement function.
         return function decrement() {
            cnt = cnt - 1;
            output.innerHTML += "The value of the cnt is: " + cnt + "<br>";
         }
      }
      const func = counter(); // Returns the decrement() function expression
      func();
      func();
      func();
   </script>
</body>
</html>
The value of the cnt is: 99
The value of the cnt is: 98
The value of the cnt is: 97

现在,让我们再次记住闭包的定义。它指出,即使外部函数的执行已完成,嵌套函数仍然可以访问外部函数作用域中的变量。

这里,counter() 函数的执行已经完成。但是,你仍然可以调用 decrement() 函数,并使用更新的值访问“cnt”变量。

让我们来看看闭包的另一个示例。

Example

在下面的示例中,name() 函数返回 getFullName() 函数。 getFullName() 函数将字符串与外部函数范围中定义的变量“name”合并。

<html>
<head>
   <title> JavaScript - Closure </title>
</head>
<body>
   <p id = "demo"> </p>
   <script>
      const output = document.getElementById("demo");
      function name() {
         let name = "John";
         return function getFullName() {
            return name + " Doe";
         }
      }

      const fullName = name();
      output.innerHTML += "The full name is " + fullName();
   </script>
</body>
</html>
The full name is John Doe

Benefits of Closure

下面是 JavaScript 中闭包的一些好处 -

  1. Encapsulation - 闭包允许开发人员隐藏或封装数据。它使数据处于私有状态,无法从全局范围内访问。所以,你可以只公开必需的变量和函数,而隐藏代码的其他内部细节。

  2. Preserving State - 即使外部函数的执行已经完成,该函数也会记住它的词法作用域。所以,开发人员可以维护状态,就如我们在上面的示例中维护计数器的状态一样。

  3. Improved memory efficiency - 闭包允许你有效地管理内存,因为你只能保留对必要变量的访问,而不需要在全局范围内定义变量。