[學習筆記] JavaScript 閉包

學習Javascript閉包(Closure)
javascript的閉包
JavaScript 閉包深刻理解(closure)
理解 Javascript 的閉包
JavaScript 中的閉包javascript

看了《JS高級程序設計》和上面幾篇文章,小總結下JS閉包~html

做爲一個初學者,學習一個新的概念無非就是知道 「它是什麼」、「什麼原理」、「怎樣建立」、「有什麼用」、「怎麼用」,這篇文章就從這幾個角度介紹閉包,歡迎小夥伴們拍磚~java


什麼是閉包

上面幾篇文章,每一篇對閉包的定義都不相同,其實這真是一個很難用語言完美描述出來的東西。我的更喜歡阮一峯寫的:編程

閉包就是可以讀取其餘函數內部變量的函數。segmentfault

在JavaScript中,函數A內部的局部變量不能被A之外的函數訪問到,只能被A內部的子函數B訪問到,當在函數A外調用函數B時,即可經過B訪問A中局部變量,那麼子函數B就是閉包。(注意,是子函數哦~)
這樣說比較抽象,舉個栗子~ruby

function outer() {

    var a = 100; // outer的局部變量

    function inner() { // 閉包
        console.log(a);
    }

    return inner; // 沒有這條語句,閉包起不到在outer外部訪問變量a的做用~
}
console.log(a); // 在外部直接訪問a出錯,Uncaught ReferenceError: a is not defined

var test = outer(); // outer運行完返回inner,賦值給test
test(); // 100,執行test(),至關於執行inner(),這樣就能夠訪問到outer內部的a了

司徒正美在blog中從另外一個角度解釋了閉包:閉包

簡單來講,閉包就是在另外一個做用域中保存了一份它從上一級函數或做用域取得的變量(鍵值對),而這些鍵值對是不會隨上一級函數的執行完成而銷燬。周愛民說得更清楚,閉包就是「屬性表」,閉包就是一個數據塊,閉包就是一個存放着「Name=Value」的對照表。就這麼簡單。可是,必須強調,閉包是一個運行期概念。函數

閉包的原理

說原理可能有些大,但這部分確實是要從內部機制的角度,解釋下「爲何上面的栗子中經過inner就能夠在outer外部訪問outer內部的a」~性能

我的認爲在JS中,有兩個鏈很重要:原型鏈做用域鏈。經過 原型鏈 能夠實現繼承,而與 閉包 相關的就是 做用域鏈學習

仍是上面的栗子,假設outer定義在全局做用域中

1  function outer() {

2     var a = 100; 

3     function inner() { 
4         console.log(a);
5     }

6     return inner; 
7  }

8  var test = outer(); 
9  test();

當執行到1處,outer函數定義,其做用域鏈中只有一個全局對象。
而後執行8,運行outer()。此時,outer的做用域鏈中有兩個對象:outer的活動對象-->全局對象
運行outer(),會執行outer裏面的3,定義inner(),此時inner的做用域鏈是:outer的活動對象-->全局對象
執行9,其實就是執行inner函數,此時其做用域鏈是:inner的活動對象-->outer的活動對象-->全局對象

由於inner的做用域鏈中有outer的活動對象,因此它能夠訪問到outer的局部變量a。

常理來講,一個函數執行完畢,其執行環境的做用域鏈會被銷燬。可是outer執行完畢後,其活動對象仍然保留在內存中,由於inner的做用域鏈仍在引用着這個活動對象。因此此時,outer的做用域鏈雖然銷燬了,可是其活動對象仍在內存中。直到test執行完畢,outer的活動對象才被銷燬。

也正由於如此,閉包只能取得包含函數中任何變量的最後一個值,即包含函數執行完畢時變量的值。改改以前的栗子~

function outer() {

    var a = 100; 

    function inner() { 
        console.log(a);
    } 

    a = a + 50; // 改變a的值

    return inner; 
}

var test = outer(); 
test(); // 150,取得的a是150,而不是100

正是由於做用域鏈,只能裏面的訪問外面的,外面的不能訪問裏面的。也是基於做用域鏈,聰明的大師們想出了閉包,使得外面的能夠訪問裏面的。掌握做用域鏈很關鍵啊~

建立閉包的方式

建立閉包最多見的方式就是在一個函數裏面建立一個函數,以前舉得栗子就是。

司徒正美大神給出了3種閉包實現:

with(obj){
    //這裏是對象閉包
}
(function(){
    //函數閉包
 })();
try{
   //...
} catch(e) {
   //catch閉包 但IE裏不行
}

閉包的做用

1. 在函數外面讀取函數的局部變量
前面一直在說這個事兒~

2. 在內存中維持一個變量

function outer() {

    var a = 100; 

    function inner() { 
        console.log(a++);
    } 

    return inner; 
}

var test = outer(); 
test(); // 100
test(); // 101
test(); // 102

栗子中a一直在內存中,每執行一次test(),輸出值加1。由於test在全局執行環境中,因此a一直在內存中,若是test在其餘執行環境中,當這個執行執行環境銷燬的時候,a就不會再在內存中了。

3. 實現面向對象中的對象
javascript並無提供類這樣的機制,可是能夠經過閉包來模擬類的機制。把父函數看成對象(object)使用,把閉包看成它的公用方法(Public Method),把內部變量看成它的私有屬性(private value)。

function Person(){
    var name = 'XiaoMing';

    return {
        setName : function(theName){
            name = theName;
        },

        getName : function(){
            return name;
        }
    };
}
var person = new Person();
console.log(person.name); // undefined
console.log(person.getName()); // XiaoMing

幾個閉包示例

參考的這幾篇文章基本都給出了閉包示例,悄悄摘選司徒正美大神的幾個放到這裏~

//*************閉包uniqueID*************

uniqueID = (function(){

    var id = 0; 
    return function() { 
        return id++; // 返回,自加
    };  
})(); 

document.writeln(uniqueID()); //0
document.writeln(uniqueID()); //1
document.writeln(uniqueID()); //2
document.writeln(uniqueID()); //3
document.writeln(uniqueID()); //4
//*************閉包階乘*************

var a = (function(n) {
    if (n<1) { 
        alert("invalid arguments"); 
        return 0; 
    }
    if(n==1) { 
        return 1; 
    }
    else { 
        return n * arguments.callee(n-1); 
    }
})(4);

document.writeln(a);
//***********實現面向對象中的對象***********

function Person(){
    var name = 'XiaoMing';

    return {
        setName : function(theName){
            name = theName;
        },

        getName : function(){
            return name;
        }
    };
}
var person = new Person();
console.log(person.name); // undefined
console.log(person.getName()); // XiaoMing
//***********避免 Lift 效應***********

var tasks = [];
for (var i = 0; i < 5; i++) {
    // 注意有一層額外的閉包
    tasks[tasks.length] = (function (i) {
        return function () {
            console.log('Current cursor is at ' + i);
        };
    })(i);
}

var len = tasks.length;
while (len--) {
    tasks[len]();
}

運行結果:

Current cursor is at 4
Current cursor is at 3
Current cursor is at 2
Current cursor is at 1
Current cursor is at 0

補充:Lift效應

var tasks = [];
for (var i = 0; i < 5; i++) {
    tasks[tasks.length] = function () {
        console.log('Current cursor is at ' + i);
    };
}

var len = tasks.length;
while (len--) {
    tasks[len]();
}

上面這段代碼輸出5

Current cursor is at 5

這一現象稱爲 Lift效應。緣由在於

在引用函數外部變量時,函數執行時外部變量的值由運行時決定而非定義時

實際編程的時候,很容易碰見lift效應,加一層閉包就能夠解決哦~

需注意的問題

因爲閉包會使得函數中的變量都被保存在內存中,內存消耗很大,因此不能濫用閉包,不然會形成網頁的性能問題,在IE中可能致使內存泄露。解決方法是,在退出函數以前,將不使用的局部變量所有刪除。
若是有很是龐大的對象,且預計會在老舊的引擎中執行,則使用閉包時,注意將閉包不須要的對象置爲空引用。


這是我對於閉包學習的總結,錯誤和不足歡迎你們指正~

相關文章
相關標籤/搜索