JavaScript - 闭包
什么是闭包?
JavaScript 中的闭包概念允许嵌套函数访问父函数作用域中定义的变量,即使父函数的执行已经结束。简而言之,你可以使用闭包将全局变量设置为本地变量或私有变量。
JavaScript 闭包本质上是函数及其词法环境的组合。这允许内部函数访问外部函数作用域。每次创建函数时,都会创建一个闭包。
在学习闭包的概念之前,你需要了解词法作用域、嵌套函数和返回函数的概念。
词法作用域
在 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 闭包的定义有点令人困惑,但我们将逐步学习闭包的概念,以便您能够清晰地理解。
计数器困境
例如,您创建了一个计数器来递增和递减变量。如下所示,您需要使用全局变量来表示计数器。
示例
在下面的示例中,全局变量"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(); // 返回 decrement() 函数表达式
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' 变量。
我们来看另一个闭包的例子。
示例
在下面的例子中,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
闭包的好处
以下是 JavaScript 中闭包的一些好处 -
-
封装 - 闭包允许开发者隐藏或封装数据。它使数据私有化,无法从全局范围访问。因此,您可以只公开所需的变量和函数,并隐藏代码的其他内部细节。
-
保存状态 - 即使外部函数执行完毕,函数也会记住其词法范围。因此,开发者可以像我们在上述示例中维护计数器状态一样维护状态。
-
提高内存效率 - 闭包允许您高效地管理内存,因为您只需要保留对必要变量的访问权限,而无需全局定义变量。

