JavaScript 閉包

JavaScript 變量屬于本地全局作用域。

全局變量能夠通過閉包實現局部(私有)。

全局變量

函數能夠訪問函數內部定義的所有變量,比如:

實例

function myFunction() {
    var a = 4;
    return a * a;
} 

親自試一試

但是函數也能訪問函數外部定義的變量,比如:

實例

var a = 4;
function myFunction() {
    return a * a;
} 

親自試一試

在最后這個例子中,a全局變量。

在網頁中,全局變量屬于 window 對象。

全局變量能夠被頁面中(以及窗口中)的所有腳本使用和修改。

在第一個例子中,a局部變量。

局部變量只能用于其被定義的函數內部。對于其他函數和腳本代碼來說它是不可見的。

擁有相同名稱的全局變量和局部變量是不同的變量。修改一個,不會改變其他。

不通過關鍵詞 var 創建的變量總是全局的,即使它們在函數中創建。

變量的生命周期

全局變量活得和您的應用程序(窗口、網頁)一樣久。

局部變量活得不長。它們在函數調用時創建,在函數完成后被刪除。

一個計數器的困境

假設您想使用變量來計數,并且您希望此計數器可用于所有函數。

您可以使用全局變量和函數來遞增計數器:

實例

// 初始化計數器
var counter = 0;
// 遞增計數器的函數
function add() {
  counter += 1;
}
// 調用三次 add()
add();
add();
add();
// 此時計數器應該是 3

親自試一試

上述解決方案有一個問題:頁面上的任何代碼都可以更改計數器,而無需調用 add()。

對于 add() 函數,計數器應該是局部的,以防止其他代碼更改它:

實例

// 初始化計數器
var counter = 0;
// 遞增計數器的函數
function add() {
  var counter = 0; 
  counter += 1;
}
// 調用三次 add()
add();
add();
add();
//此時計數器應該是 3。但它是 0。

親自試一試

它沒有用,因為我們顯示全局計數器而不是本地計數器。

通過讓函數返回它,我們可以刪除全局計數器并訪問本地計數器:

實例

// 遞增計數器的函數
function add() {
  var counter = 0; 
  counter += 1;
  return counter;
}
// 調用三次 add()
add();
add();
add();
//此時計數器應該是 3。但它是 1。

親自試一試

它沒有用,因為我們每次調用函數時都會重置本地計數器。

JavaScript 內部函數可以解決這個問題。

JavaScript 嵌套函數

所有函數都有權訪問全局作用域。

事實上,在 JavaScript 中,所有函數都有權訪問它們“上面”的作用域。

JavaScript 支持嵌套函數。嵌套函數可以訪問其上的作用域。

在本例中,內部函數 plus() 可以訪問父函數中的 counter 計數器變量:

實例

function add() {
    var counter = 0;
    function plus() {counter += 1;}
    plus();     
    return counter; 
}

親自試一試

這樣即可解決計數器困境,如果我們能夠從外面訪問 plus() 函數。

我們還需要找到只執行一次 counter = 0 的方法。

我們需要閉包(closure)。

JavaScript 閉包

記得自調用函數嗎?這種函數會做什么呢?

實例

var add = (function () {
    var counter = 0;
    return function () {return counter += 1;}
})();
add();
add();
add();
// 計數器目前是 3 

親自試一試

例子解釋

變量 add 的賦值是自調用函數的返回值。

這個自調用函數只運行一次。它設置計數器為零(0),并返回函數表達式。

這樣 add 成為了函數。最“精彩的”部分是它能夠訪問父作用域中的計數器。

這被稱為 JavaScript 閉包。它使函數擁有“私有”變量成為可能。

計數器被這個匿名函數的作用域保護,并且只能使用 add 函數來修改。

閉包指的是有權訪問父作用域的函數,即使在父函數關閉之后。