什么是閉包及使用閉包應該注意的地方


  對於新手來說,閉包的概念往往有些晦澀。書上的概念描寫的非常簡短,網上的各類文章又常常是長篇大論地探討閉包的作用呀,影響呀,看到最后,覺得是越看越暈,越來越搞不懂什么是閉包了,所以在這里分享自己對閉包的淺顯的理解,希望對新手有所幫助,相互交流。

>>進入主題

  什么是閉包:閉包是指有權訪問另一個函數作用域的變量的函數。--《javascript高級程序設計》

  書上的概念就這么一句話,其實閉包就是像這句話定義的一樣簡單。如果有一個函數fun2,它可以訪問在其它函數如fun1中的局部變量,那么它(fun2)就是閉包。創建閉包的簡單方式,就在在函數內部創建另一個函數,下面例子創建了個簡單的閉包

1 function fun1 () {
2     var  a = 0;
3     function fun2 () { 
4         console.log(a); // 在這個函數fun2中可以訪問另一個函數中的變量a,所以fun2()就是一個閉包。
5     }
6     fun2();
7 }

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~定義分割線~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

  以下是關於閉包的一些知識與注意事項:

  1.在定義函數外調用閉包的方法,逃離方式:

    (1).將內部函數指定給一個全局變量;

var globalVar;
function outer() {
    console.log('outer');
    function inner(){
        console.log('inner');
    }
    globalVar = inner;      
}
outer(); // outer
globalVar(); // inner;

在這個例子中inner()通過全局變量的引用成功逃離,現在可以在全局中調用,而且可以引用outer()的變量

    (2).通過返回值來'營救'內部函數的引用

function outer() {
    console.log('outer');
    function inner(){
        console.log('inner');
    }
   return inner;      
}
var fn = outer(); // outer
fn(); // inner;

在這個例子中inner()通過返回值成功逃離,現在可以在全局中調用,而且可以引用outer()的變量

  2.在函數外調用閉包的影響:增加內存占用;

    本來正常的情況下是函數調用結束之后函數的執行環境離開環境棧,定義的變量廢棄(廢棄與垃圾收集機制有關),活動變量(變量對象)會被銷毀,內存釋放。但是現在因為閉包的作用域鏈包含了外部函數的變量對象,外部函數的變量有可能再被引用,垃圾收集機制不會將外部函數的變量廢棄,在內存保留的外部函數的變量對象。這樣就加大了對內存的占用。

  3.閉包與變量的關系:閉包中常見的誤區及解決技巧

    閉包保存的是包含函數的整個變量對象,所取得的外部對象變量為閉包被調用時刻的對象變量,一般為外部函數變量的最后一個值。

例:

 

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

  這里外部函數的返回值為一個數組,數組值為不同函數(閉包)的引用,我們會誤認為每個閉包的調用的返回值不同,但實際上每個函數都會返回一樣的值。因為當閉包調用時,調用閉包的外部函數已經執行完畢,此時外部函數的變量對象中的 i = 10,而我們閉包的返回值為i,閉包會獲取調用時的外部變量對象,此時的i為10。

解決:

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

  在循環中,我們定義了一個匿名數組,並將立即執行該匿名函數的結果賦給數組,這里的匿名函數有一個參數num,每次將i作為參數傳遞給num,每次循環num都會得到不同的值,所以每次返回了不同的函數(區別在於num值不同),當在外部調用數組值時,會返回不同的值,與預期相符。 

  4. 注意閉包中this值

  首先,關於函數中this指向,我們應該知道this指向調用該函數的對象,若無明確調用對象則指向window對象。

  在閉包中容易弄錯的this指向,例:

var    name = "window";
    var o = {
        name: "object",
        getName: function() {
            return function() {
                return this.name;
            };
        }
    };
console.log(o.getName()()); // window

  可以看出閉包this指向了全局對象,分析,可以把o.getName()()寫成(o.getName())(),這個表達式相當於第一步先執行了o.getName(),這個函數返回了一個匿名函數(閉包),然后在全局下執行了這個閉包,並不是通過對象o調用,所以this指向全局對象。

  5. 內存泄漏的問題,如何減少不必要的內存占用

function assignHandler() {
        var ele = documnet.getElementById("somenode");
        ele.onclick = function() {
            console.log(ele.id);
        };
} 

  在上面的例子中定義ele的方法與匿名函數有關,於是ele保存了對匿名函數的引用,而閉包會引用包含函數也引用了ele對象,這樣就造成了對象的循環引用,ele元素(dom元素占用內存較大)就一直保存在了內存中無法釋放,解決方法如下:

  

function assignHandler() {
        var ele = documnet.getElementById("somenode");
        var id = ele.id;     
        ele.onclick = function() {
            console.log(id);    // 通過id值中介表面上解除了與ele的循環引用
        };
        ele = null; // 手動解除引用
} 

  因為閉包會引用包含函數的整個變量對象,所以用id值只能表面上解除循環引用,需要進一步手動設置元素為null才能真正減少引用,釋放內存。

  以上大多為使用閉包應該注意的事項,當然使用閉包也有很多優點,如:使用匿名函數模仿塊級作用域,定義訪問私有變量的特權方法,避免全局變量污染等,大家可以自行研究。

 

  

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM