JavaScript - 闭包



什么是闭包?

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

JavaScript 闭包基本上是函数及其词法环境的组合。这允许内部函数访问外部函数范围。每次在函数创建时创建函数时,都会创建一个 Closure。

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

词法范围

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

嵌套函数

您可以在函数内部定义函数,内部函数称为嵌套函数。让我们通过下面的示例来学习它。

在下面的示例中,我们在 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!

返回函数

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

在下面的代码中,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)

例如,您创建计数器来递增和递减变量。如下所示,您需要对 counter 使用全局变量。

在下面的示例中,全局变量 '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 闭包出现了。

示例:JavaScript 闭包

在下面的示例中,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; // 用作递减函数的全局变量。
	 	 	 	 	return function decrement() {
	 	 	 	 	 	 cnt = cnt - 1;
	 	 	 	 	 	 output.innerHTML += "The value of the cnt is: " + cnt + "<br>";
	 	 	 	 	}
	 	 	 }
	 	 	 const func = counter(); // 返回递减()函数表达式
	 	 	 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

现在,让我们再次记住 closure 的定义。它表示嵌套函数可以从外部函数的作用域访问变量,即使外部函数的执行已完成。

在这里,counter() 函数的执行结束了。不过,您可以调用 decrement() 函数并使用更新的值访问 'cnt' 变量。

让我们看另一个 closure 的例子。

在下面的示例中,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

闭包(Closure)的好处

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

  • 封装 − 闭包允许开发人员隐藏或封装数据。它使数据私有化,并且无法从全局范围访问。因此,您可以只公开所需的变量和函数,并隐藏代码的其他内部详细信息。
  • 保留状态 − 即使外部函数的执行已完成,函数也会记住其词法范围。因此,开发人员可以像在上面示例中维护计数器的状态一样维护状态。
  • 提高内存效率 − 闭包允许您有效地管理内存,因为您只能保留对必要变量的访问权限,而不需要全局定义变量。