JavaScript - 闭包
什么是闭包?
JavaScript 中的 closures 概念允许嵌套函数访问父函数作用域中定义的变量,即使父函数的执行已经结束。简而言之,你可以使用闭包将全局变量变为局部或私有变量。
JavaScript closure 基本上是函数与其 lexical environment 的组合。这允许内部函数访问外部函数的作用域。每次创建函数时都会在函数创建时生成一个闭包。
在开始学习闭包概念之前,你需要了解 lexical scoping、嵌套函数和返回函数的概念。
Lexical Scoping
在 JavaScript 中,lexical scoping 是一个概念,其中变量的作用域在代码编译时根据代码结构确定。例如,嵌套函数可以访问父函数作用域中的变量,这就称为 lexical scoping。
嵌套函数
你可以在函数内部定义函数,内部函数称为嵌套函数。让我们通过下面的示例来学习它。
示例
在下面的示例中,我们在 outer() 函数内部定义了 inner() 函数。而且,inner() 函数也在 outer() 函数内部执行。
当我们执行 outer() 函数时,它也会执行嵌套函数 inner()。
<html>
<body>
<p id = "demo"> </p>
<script>
const output = document.getElementById("demo");
function outer() {
output.innerHTML += "外部函数被执行! <br>";
function inner() {
output.innerHTML += "内部函数被执行! <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 += "外部函数被执行! <br>";
return function inner() {
output.innerHTML += "内部函数被执行! <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() function 时,它会将 '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() function 而改变它。
在这里,JavaScript closure 就派上用场了。
示例:JavaScript Closures
在下面的示例中,counter() function 返回 decrement() function。'cnt' 变量定义在 counter() function 内部,而不是在全局作用域中。
decrement() function 将 'cnt' 的值减少 1 并在输出中打印。
'func' 变量包含 decrement() function expression。每当你执行 func() 时,它就会调用 decrement() function。
<html>
<body>
<p id = "demo"> </p>
<script>
const output = document.getElementById("demo");
function counter() {
let cnt = 100; // 作为 decrement function 的全局变量。
return function decrement() {
cnt = cnt - 1;
output.innerHTML += "The value of the cnt is: " + cnt + "<br>";
}
}
const func = counter(); // 返回 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
现在,让我们再次回忆 closure 的定义。它说,即使外部 function 的执行已经结束,嵌套 function 仍然可以访问外部 function 作用域中的变量。
在这里,counter() function 的执行已经结束。但你仍然可以调用 decrement() function 并访问具有更新值的 'cnt' 变量。
让我们看另一个 closure 示例。
示例
在下面的示例中,name() function 返回 getFullName() function。getFullName() function 将字符串与定义在外部 function 作用域中的 '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 中闭包的一些好处 −
封装 − 闭包允许开发者隐藏或封装数据。它使数据私有化,无法从全局作用域访问。因此,您可以只暴露必要的变量和函数,并隐藏代码的其他内部细节。
保存状态 − 即使外部函数执行完成,函数仍然记住其词法作用域。因此,开发者可以维护状态,就像上面示例中维护计数器的状态一样。
提高内存效率 − 闭包允许您高效管理内存,因为您只需保留对必要变量的访问,而无需将变量定义为全局变量。