JavaScript 闭包
JavaScript 变量可以属于局部或全局作用域。
通过闭包,全局变量可以变成局部(私有)的。
全局变量
一个 function
可以访问函数内部定义的所有变量,如下例所示:
但是一个 function
也可以访问函数外部定义的变量,如下例所示:
在最后一个例子中,a 是一个全局变量。
在网页中,全局变量属于该页面。
页面上的所有其他脚本都可以使用(并更改)全局变量。
在第一个例子中,a 是一个局部变量。
局部变量只能在定义它的函数内部使用。它对其他函数和其他脚本代码是隐藏的。
同名的全局变量和局部变量是不同的变量。修改一个,不会修改另一个。
注意
不带声明关键字(var
, let
, 或 const
)创建的变量始终是全局的,即使它们在函数内部创建。
变量的生命周期
全局变量一直存在,直到页面被销毁,例如导航到另一个页面或关闭窗口。
局部变量的生命周期很短。当函数被调用时创建它们,当函数结束时删除它们。
计数器困境
假设您想使用一个变量来计数某件事,并且您希望所有函数都能访问此计数器。
您可以使用一个全局变量和一个 function
来增加计数器。
示例
// 初始化计数器
let counter = 0;
// 递增计数器的函数
function add() {
counter += 1;
}
// 调用 add() 3 次
add();
add();
add();
// counter 现在应该是 3
自己动手试一试 »
上面的解决方案有一个问题:页面上的任何代码都可以更改 counter,而无需调用 add()。
counter 应该对 add()
函数是局部的,以防止其他代码更改它。
示例
// 初始化计数器
let counter = 0;
// 递增计数器的函数
function add() {
let counter = 0;
counter += 1;
}
// 调用 add() 3 次
add();
add();
add();
// counter 现在应该是 3。但它是 0。
自己动手试一试 »
它不起作用,因为我们显示的是全局 counter 而不是局部 counter。
我们可以删除全局 counter,并通过让函数返回它来访问局部 counter。
示例
// 递增计数器的函数
function add() {
let counter = 0;
counter += 1;
return counter;
}
// 调用 add() 3 次
add();
add();
add();
// counter 现在应该是 3。但它是 1。
自己动手试一试 »
它不起作用,因为我们每次调用函数时都会重置局部 counter。
JavaScript 的内部函数可以解决这个问题。
JavaScript 嵌套函数
所有函数都可以访问全局作用域。
实际上,在 JavaScript 中,所有函数都可以访问它们“上方”的作用域。
JavaScript 支持嵌套函数。嵌套函数可以访问它们“上方”的作用域。
在这个例子中,内部函数 plus()
可以访问父函数中的 counter
变量。
示例
function add() {
let counter = 0;
function plus() {counter += 1;}
plus();
return counter;
}
自己动手试一试 »
如果我们可以从外部访问 plus()
函数,这就可以解决计数器困境。
我们还需要找到一种方法只执行一次 counter = 0
。
我们需要一个闭包。
JavaScript 闭包
还记得自调用函数吗?这个函数有什么作用?
示例
const add = (function () {
let counter = 0;
return function () {counter += 1; return counter}
})();
add();
add();
add();
// counter 现在是 3
自己动手试一试 »
示例解释
add
变量被赋值给自调用函数的返回值。
自调用函数只运行一次。它将 counter 设置为零 (0),并返回一个函数表达式。
这样,add 就变成了一个函数。它的“奇妙”之处在于它可以访问父作用域中的 counter。
这就是所谓的 JavaScript **闭包**。它使得函数可以拥有“**私有”变量。
counter 被匿名函数的范围保护,并且只能通过 add 函数进行更改。
闭包是一个函数,即使父函数已经关闭,它仍然可以访问父作用域。