JavaScript 闭包
JavaScript 变量可以属于 **局部** 或 **全局** 作用域。
全局变量可以使用 **闭包** 变为局部(私有)。
全局变量
一个 function
可以访问在函数 **内部** 定义的所有变量,例如
但一个 function
也可以访问在函数 **外部** 定义的变量,例如
在最后一个示例中,**a** 是一个 **全局** 变量。
在网页中,全局变量属于该网页。
全局变量可以被网页中的所有其他脚本使用(和修改)。
在第一个示例中,**a** 是一个 **局部** 变量。
局部变量只能在定义它的函数内部使用。它对其他函数和其他脚本代码是隐藏的。
具有相同名称的全局变量和局部变量是不同的变量。修改其中一个不会修改另一个。
注意
**没有** 使用声明关键字 (var
, let
, 或 const
) 创建的变量始终是全局的,即使它们是在函数内部创建的。
变量生命周期
全局变量一直存在,直到页面被丢弃,例如,当你导航到另一个页面或关闭窗口时。
局部变量的生命周期很短。它们在函数被调用时创建,并在函数结束时被删除。
计数器难题
假设你想使用一个变量来计算某些东西,并且你想让这个计数器对所有函数都可用。
你可以使用一个全局变量和一个 function
来增加计数器
示例
// 初始化计数器
let counter = 0;
// 用于增加计数器的函数
function add() {
counter += 1;
}
// 调用 add() 3 次
add();
add();
add();
// 计数器现在应该是 3
亲自试一试 »
上面的解决方案存在一个问题:页面上的任何代码都可以更改计数器,而无需调用 add()。
计数器应该在 add()
函数中是局部的,以防止其他代码更改它。
示例
// 初始化计数器
let counter = 0;
// 用于增加计数器的函数
function add() {
let counter = 0;
counter += 1;
}
// 调用 add() 3 次
add();
add();
add();
// 计数器现在应该为 3。但它为 0
亲自试一试 »
它没有起作用,因为我们显示了全局计数器而不是局部计数器。
我们可以删除全局计数器,并通过让函数返回它来访问局部计数器。
示例
// 用于增加计数器的函数
function add() {
let counter = 0;
counter += 1;
return counter;
}
// 调用 add() 3 次
add();
add();
add();
// 计数器现在应该为 3。但它为 1。
亲自试一试 »
它没有起作用,因为我们每次调用函数时都会重置局部计数器。
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();
// 计数器现在为 3
亲自试一试 »
示例解释
变量 add
被赋值给自执行函数的返回值。
自执行函数只运行一次。它将计数器设置为零 (0),并返回一个函数表达式。
这样 add 就变成了一个函数。“奇妙”之处在于它可以访问父作用域中的计数器。
这被称为 JavaScript **闭包**。它使函数能够拥有 **“私有”** 变量。
计数器受匿名函数的作用域保护,只能使用 add 函数更改。
闭包是一个函数,即使在父函数关闭后,它仍然可以访问父作用域。