闭包的概念允许嵌套函数访问其父函数作用域中定义的变量,即使父函数的执行已经完成。简单来说,你可以使用闭包将全局变量变为局部或私有的。
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();
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 中闭包的一些好处:
-
封装 — 闭包允许开发者隐藏或封装数据。它使数据私有化,无法从全局作用域访问。因此,你可以只暴露必要的变量和函数,而隐藏代码的其他内部细节。
-
保持状态 — 函数会记住它的词法作用域,即使外部函数的执行已经完成。因此,开发人员可以维护状态,就像我们在上面的例子中维护计数器的状态一样。
-
提高内存效率 — 闭包允许有效地管理内存,因为你可以只保留对必要变量的访问权限,而不需要全局定义变量。