JS 閉包

JS 閉包

JS編程的時候你必定遇到過這個問題:局部變量實現累加,看下面例子:javascript

function aotuadd(){
    var a=1;
    a++;
    console.log(a);
}
aotuadd();//2
autuadd();//2

上面的代碼沒法實現累加,這時可能有的人就會選擇把a放在全局做用域中,能實現累加功能,可是會使全局變量增多,這是咱們不想看到的。
其實之因此把a放在全局做用域中,是由於autoadd函數的做用域被全局做用域包裹,因此咱們能夠在全局做用域中取值;
那麼咱們是否是能夠給autoadd外層再包裹一個做用域(假設是wapper),而後將這個a放在wapper做用域中,問題不就解決了嘛。
咱們既能訪問wrapper中的a,又沒必要增長全局變量。由於js中只有函數可以產生做用域,因此其實就是再aotoadd外包裹一個wrapper函數,試着寫一下:java

function wrapper(){
    var a=1;
    function autoadd(){
        a++;
        console.log(a);
    }
}
wrapper();

寫到這裏發現,咱們沒法訪問autoadd,怎麼解決:node

function wrapper(){
    var a=1;
    function autoadd(){
        a++;
        console.log(a);
    }
    window.bar=autoadd;
}
wrapper()
bar();//2
bar();//3

上面這種方法是可以解決沒法調用的問題的,可是這回到了咱們最開始遇到的問題,增長了全局變量/函數,這是咱們不想看到的;另外一種解決方法:編程

function wrapper(){
    var a=1;
    function autoadd(){//必要條件
        a++;//必要條件
        console.log(a);
    }
    return autoadd;
}
var x=wrapper()//返回一個函數,巧合的是,返回的這個函數體中,還有一個變量a要引用wrapper做用域下的a,因此這個a不能銷燬,wrapper()上下文環境不被銷燬,依然存在於執行上下文棧中;
x();
x();

經過返回函數的方法進行調用,上面的這種寫法就是咱們最多見的閉包的寫法,也就是說,閉包的產生,其實並非必定依賴於「返回函數」這個條件,只不過不經過這種方法調用有違初衷;閉包

看懂了上面這個例子,閉包的概念也呼之欲出:閉包是指有權訪問另外一個函數做用域中的變量的函數,即當前做用域總能訪問外部做用域中的變量。
你可能常常看到這句話:「建立閉包的最多見的方式就是在一個函數內建立另外一個函數,經過另外一個函數訪問這個函數的局部變量」。其實我以爲偏偏就是這句話,致使不少人沒法理解閉包,換成下面這種說法更好理解:建立閉包的最多見的方式是在一個函數外部包裹另外一個函數,經過在另外一個函數內部定義變量的方式,使咱們想要的變量駐留在外層函數中,減小全局變量。app

閉包的兩個必要條件:函數外層有函數/ 內層函數要使用外層函數中的變量異步

咱們總結一下,上面var x=wrapper()和function warpper()能夠利用當即執行函數合寫,進一步減小全局變量:模塊化

var x=(function(){
        var a=1;
        return function(){
            a++;
            console.log(a);
        }
})();
x();//2
x();//3
x=null;//解除引用,等待垃圾回收

或者:函數

function Myobj(){
    var age=1;;
    this.autoadd=function(){
        age++;
        console.log(age);
        //return age;
    }
}
var obj=new Myobj();
obj.autoadd();
obj.autoadd();

另外一個常見問題:
for循環給網頁中一連串元素綁定,例如onclick事件:字體

var fn = function() {
        var divs = document.querySelectorAll('div');
        for (var i = 0; i < 3; i++) {
                divs[i].onclick = function() {
                        alert(i);
                };
        }
};
fn();

點擊每一個div都會彈出3。這是爲何呢?
咱們先來分析一下緣由:onclick事件是一個異步回調函數的指針,並不會當即執行,上面的函數表達式,並不會進行變量賦值。只有在調用一個函數時,一個新的執行上下文才會被建立出來。那麼咱們是否是能夠經過調用函數的方法,來建立多個新的執行上下文環境,建立新的做用域,這樣不一樣的調用就能夠有不一樣的參數。那麼解決思路也是外層包裹function的方法,即利用閉包:

var fn = function() {
        var divs = document.querySelectorAll('div');
        for (var i = 0; i < 3; i++) {
                divs[i].onclick = (function(a) {//當即執行函數,建立新的執行上下文
                        alert(a);
                })(i);
        }
};
fn();

或者:

var fn = function() {
        var divs = document.querySelectorAll('div');
        for (var i = 0; i < 3; i++) {
                (function(i){//當即執行函數,建立新的執行上下文,將i駐留在內存中
                    divs[i].onclick = function() {
                        alert(i);
                })(i);
        }
};
fn();

另外一種解決方法:(利用事件代理)

var ul=document.querySelector('ul');
var lis=ul.querySelectorAll('ul li');
ul.addEventListener('click', function (e) {
    var target= e.target;
    if(target.nodeName.toUpperCase()==='LI'){
        alert([].indexOf.call(lis,target));
    }
},false)

理解閉包的關鍵就是下面這句:
閉包:當一個函數在定義它的做用域之外的地方被調用時,它訪問的依然是定義它時的做用域。這種現象稱之爲閉包。
JavaScript中的函數運行在它們被定義的做用域裏,而不是它們被執行的做用域裏。——《JavaScript語言精粹》


閉包的優勢:
1)使變量駐留在內存中(多了變缺點);
2)避免全局變量污染;
3)私有化變量;
閉包的缺點:
1)由於閉包會攜帶包含它的函數的做用域,因此比其餘函數佔用更多內存;
2)使用不當會形成內存泄漏;


閉包應用場景(來自《javascript高級程序設計》)
1.使用閉包能夠在JS中模擬塊級做用域(ECMAScript6標準以前的JavaScript自己沒有塊級做用域的概念);

function outputNumbers(count){
      (function(){
               for(var i = 0; i < count; i++){
               alert(i);
               }
      })();
  alert(i); //致使一個錯誤!
}

2.閉包能夠用於在對象中建立私有變量;

// 1.2.閉包能夠用於在對象中建立私有變量

function MyObject(){
    // 私有變量和私有函數
    var privateVariable = 10;
    function privateFunction(){
      return false;
    }
    // 特權方法,調用私有方法、函數
    this.publicMethod = function(){
      privateVariable++;
      return privateFunction();
    }
  }

閉包的運用
1.匿名自執行函數
咱們在實際狀況下常常遇到這樣一種狀況,即有的函數只須要執行一次,其內部變量無需維護,好比UI的初始化,那麼咱們可使用閉包:

//將所有li字體變爲紅色
(function(){    
    var els = document.getElementsByTagName('li');
    for(var i = 0,lng = els.length;i < lng;i++){
        els[i].style.color = 'red';
    }    
})();

咱們建立了一個匿名的函數,並當即執行它,因爲外部沒法引用它內部的變量,
所以els,i,lng這些局部變量在執行完後很快就會被釋放,節省內存!
關鍵是這種機制不會污染全局對象。
2. 實現封裝/模塊化代碼

var person= function(){    
    //變量做用域爲函數內部,外部沒法訪問    
    var name = "default";       
return {    
       getName : function(){    
           return name;    
       },    
       setName : function(newName){    
           name = newName;    
       }    
    }    
}();
console.log(person.name);//直接訪問,結果爲undefined    
console.log(person.getName());  //default 
person.setName("jozo");    
console.log(person.getName());  //jozo

3. 實現面向對象中的對象
這樣不一樣的對象(類的實例)擁有獨立的成員及狀態,互不干涉。雖然JavaScript中沒有類這樣的機制,可是經過使用閉包,
咱們能夠模擬出這樣的機制。仍是以上邊的例子來說:

function Person(){    
    var name = "default";       
return {    
       getName : function(){    
           return name;    
       },    
       setName : function(newName){    
           name = newName;    
       }    
    }    
};    
var person1= Person();    
print(person1.getName());    
john.setName("person1");    
print(person1.getName());  // person1  
var person2= Person();    
print(person2.getName());    
jack.setName("erson2");    
print(erson2.getName());  //person2

Person的兩個實例person1 和 person2 互不干擾!由於這兩個實例對name這個成員的訪問是獨立的 。

初學js不少理解不到位的地方,望批評指正!

相關文章
相關標籤/搜索