1. 程式人生 > >淺談對js的閉包的理解

淺談對js的閉包的理解

首先理解閉包就要先理解全域性作用域和區域性作用域的區別。函式內部可以訪問全域性作用域下定義的全域性變數,而函式外部卻無法訪問到函式內部定義的區域性變數

如果需要得到函式內部的區域性變數,只有通過在函式的內部,再定義一個函式。比如:

一、function f1(){
    var a=1;
    function f2(){
        return a;
    }
    return f2;
}
var result=f1();
console.log(result()); //1
二、function f1(){
    var a=1;
    return function(){
        return a;
    };
}
var result=f1();
console.log(result()) //1

上面程式碼中,兩種寫法相同,唯一的區別是內部函式是否是匿名函式。函式f2就在函式f1內部,這時f1內部的所有區域性變數,對f2都是可見的。但是反過來就不行,f2內部的區域性變數,對f1就是不可見的。這就是JavaScript語言特有的”鏈式作用域”結構(chain scope),子物件會一級一級地向上尋找所有父物件的變數。所以,父物件的所有變數,對子物件都是可見的,反之則不成立。函式f1的返回值就是函式f2,由於f2可以讀取f1的內部變數,所以就可以在外部獲得f1的內部變量了。

閉包就是函式f2,即能夠讀取其他函式內部變數的函式。由於在JavaScript語言中,只有函式內部的子函式才能讀取內部變數,因此可以把閉包簡單理解成“定義在一個函式內部的函式”。閉包最大的特點,就是它可以“記住”誕生的環境,比如f2記住了它誕生的環境f1,所以從f2可以得到f1的內部變數。

閉包可以使得它誕生環境一直存在。看下面一個例子,閉包使得內部變數記住上一次呼叫時的運算結果。

function f1(num) {
    return function() {
        return num++;
    };
}
var result = f1(2);
console.log(result()) //2
console.log(result()) //3
console.log(result()) //4

上面程式碼中,引數num其實就相當於函式f1內部定義的區域性變數。通過閉包,num的狀態被保留了,每一次呼叫都是在上一次呼叫的基礎上進行計算。從中可以看到,閉包result使得函式f1的內部環境,一直存在。

通過以上的例子,總結一下閉包的特點:

1:在一個函式內部定義另外一個函式,並且返回內部函式或者立即執行內部函式。

2:內部函式可以讀取外部函式定義的區域性變數

3:讓區域性變數始終儲存在記憶體中。也就是說,閉包可以使得它誕生環境一直存在。

閉包的另一個用處,是封裝物件的私有屬性和私有方法。

function Keith(name) {
    var age;
    function setAge(n) {
        age = n;
    }
    function getAge() {
        return age;
    }
    return {
        name: name,
        setAge: setAge,
        getAge: getAge
    };
}
var person = Keith('keith');
person.setAge(21);
console.log(person.name); // 'keith'
console.log(person.getAge()); //21

立即呼叫的函式表示式(IIFE)

通常情況下,只對匿名函式使用這種“立即執行的函式表示式”。它的目的有兩個:一是不必為函式命名,避免了汙染全域性變數;二是IIFE內部形成了一個單獨的作用域,可以封裝一些外部無法讀取的私有變數。

迴圈中的閉包

一個常見的錯誤出現在迴圈中使用閉包,假設我們需要在每次迴圈中呼叫迴圈序號

for(var i=0;i<10;i++){
    setTimeout(function(){
        console.log(i); //10
    }, 1000)
}

上面程式碼中,不會符合我們的預期,輸出數字0-9。而是會輸出數字10十次。

當匿名函式被呼叫的時候,匿名函式保持著對全域性變數 i 的引用,也就是說會記住i迴圈時執行的結果。此時for迴圈結束,i 的值被修改成了10。

為了得到想要的效果,避免引用錯誤,我們應該使用IIFE來在每次迴圈中建立全域性變數 i 的拷貝。

for(var i = 0; i < 10; i++) {
     (function(e) {
         setTimeout(function() {
             console.log(e); //1,2,3,....,10
         }, 1000);
     })(i);
 }
外部的匿名函式會立即執行,並把 i 作為它的引數,此時函式內 e 變數就擁有了 i 的一個拷貝。當傳遞給 setTimeout 的匿名函式執行時,它就擁有了對 e 的引用,而這個值是不會被迴圈改變的。