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 函式進行更改。
閉包是一個函式,即使父函式已經關閉,它仍然可以訪問父作用域。