1. 程式人生 > 實用技巧 >JS閉包示例解析

JS閉包示例解析

  作用域鏈的這種配置機制引出了一個值得注意的副作用,即閉包只能取得包含函式中任何變數的最後一個值。別忘了閉包所儲存的是整個變數物件,而不是某個特殊的變數。下面這個例子可以清晰地說明這個問題。

function createFunction(){
    var result = new Array();

    for (var i=0;i<10;i++){
        result[i] = function(){
            return i;
        }
    }
    return result;
}

  這個函式會返回一個函式陣列。從表面上看,似乎每個函式都應該返回自己的索引值。即位置0的函式返回0,位置1的函式返回1,以此類推。但實際上,每個函式都會返回10,因為每個函式的作用域鏈中都儲存著createFunction()函式的活動物件,所以他們應用的都是同一個變數i。當createFunction()函式返回後,變數i的值是10,此時每個函式都引用這儲存變數i的同一個變數物件。所以在每一個函式內部i的值都是10.但是,我們可以建立另一個匿名函式強制讓閉包的行為符合預期,如下所示:

程式碼一(A):(出自javascript高階程式設計第三版181頁閉包)

function creatFunction(){
    var result = new Array();

    for(var i=0;i<10;i++){
        result[i] = function(num){
            return function(){
                console.log(num);
                return num;
            }
        }(i)
    }
    return result;
}
creatFunction();

  現在程式碼A可以讓每個函式返回自己的索引值了,在這個版本中,我們沒有把閉包直接賦值給陣列,而是定義了一個匿名函式,並將立即執行該匿名函式的結果賦值給陣列。這裡的匿名函式有一個引數num,也就是最終的函式要返回的值。在呼叫每個匿名函式時,我們傳入了變數i由於函式引數都是按值傳遞的,所以就會將變數i的當前值複製給引數num,而在這個匿名函式內部,又建立並返回了一個訪問num的閉包。這樣一來,result陣列中的每個函式都有自己num變數的一個副本,因此就可以返回各自不同的數值了。

  現在我們來看看程式碼A和程式碼B的區別:
程式碼二(B):

function creatFunction(){
    var result = new Array();

    for(var i=0;i<10;i++){
        result[i] = function(){
            return function(num){
                console.log(num);
                return num;
            }(i)
        }
    }
    return result;
}
creatFunction();

  A程式碼和B程式碼的大致意思是在creatFunction函式內部把function物件陣列賦值給result並返回,但A程式碼的
result[i] = function(num){...}(i)的意思是將外部作用域的變數i賦值給num形參,相當於呼叫這個匿名函式。

  我們先看幾個簡單的例子:

alert((function(x,y){return x+y;})(2,3));// "5" 
alert((new Function("x","y","return x*y;"))(2,3));// "6" 
alert((function(x,y){return x+y;})(2,3));// "5"
alert((new Function("x","y","return x*y;"))(2,3));// "6"

  很多人或許會奇怪,為什麼這種方法能成功呼叫呢?覺得這個應用奇怪的人就看一下我以下這段解釋吧。

  大家知道小括號的作用嗎?小括號能把我們的表示式組合分塊,並且每一塊,也就是每一對小括號,都有一個返回值。這個返回值實際上也就是小括號中表達式的返回值。所以,當我們用一對小括號把匿名函式括起來的時候,實際上小括號對返回的,就是一個匿名函式的Function物件。因此,小括號對加上匿名函式就如同有名字的函式般被我們取得它的引用位置了。所以如果在這個引用變數後面再加上引數列表,就會實現普通函式的呼叫形式。

  所以程式碼B相當於,注意這裡的相當是指的最終的結果相當,由於最內層的return沒有加(i),返回值會是個函式物件,如果像程式碼B一樣加上(i),就會執行這個返回的函式物件內部的語句,即輸出並返回一個值:

function creatFunction(){
    var result = new Array();

    for(var i=0;i<10;i++){
        result[i] = function(){
            return function(){
                console.log(num);
                return num;
            }
        }
    }
    return result;
}
creatFunction();