前端學習筆記之閉包——看了一張圖終於明白啥是閉包了

閉包

閉包定義:指擁有多個變量和綁定了這些變量的環境的表達式(一般是一個函數),於是這些變量也是該表達式的一部分。
函數內部能夠直接讀取全局變量。
函數內部變量沒法在函數外部訪問。
函數內部聲明要用var或者let聲明,否則會變成全局變量
鏈式做用域:子對象會一級級向上尋找父對象的變量,父對象的變量子對象都是可見的,反之則不行。
在一個閉包環境內修改變量值,不會影響另外一個閉包中的變量。
普通的函數內嵌,內部函數是先執行;而閉包則是:先把內部函數賦給外部函數,而後在執行。javascript

下面這段代碼就是一根典型的閉包html

function f1(){
    var a = 10;
    function f2(){
        alert(a);
    }
    f2();    //①
}
f1();        //10  ②

f1f1()的區別不加括號是代碼,加()是執行這段代碼,加return是返回一個值,能夠把返回的值賦值給變量,不加return默認返回undefinedjava

因此處有三種寫法:segmentfault

第一種:處寫f2();處調用須要這樣寫f1();。具體執行過程:f1體內調用f2函數,並執行。閉包

第二種:處寫return f2();處調用需這樣寫f1();。具體執行過程:同上;區別是多了個return,由於如今f2函數中沒有返回值,因此f1在調用f2只是執行一下alert(a)f1的返回值是undefined函數

第三種:處寫return f2;處調用需這樣寫f1()();。這裏返回的是f2函數的代碼,因此在調用f1時要加上2個括號,第一個括號是執行f1函數,第2個括號是執行f2函數,若是處省略return會報錯。學習

return和函數調用時是否加括號的意思都明白,可是把它倆結合起來,就搞不清了。this

正好今天學閉包時碰上了,順便就把它搞清楚了。spa

到底什麼是閉包

對於新人(固然了是說我了),看不少閉包的定義,代碼,仍是不知啥是閉包,雲裏霧裏的,這裏感謝方方老師的文章JS 中的閉包是什麼?,看完後,雖然仍是說不出啥是閉包,但如今已經知道啥是閉包了,果真用圖說話最牛逼。(圖在文章中,我就不放出來了)3d

閉包的應用

MDN 上這個例子也寫的很好

調用Counter.value()時,返回的是Counter內部的變量privateCounter
increment內部沒有返回值,這個方法只是執行了privateCounter + 1操做,沒有返回值;
同理decrement是將privateCounter - 1,也沒有返回值;
因此執行Counter.increment,會返回undefined,可是接着操做Counter.value()時就能夠獲得1,由於執行上一步Counter.incrementprivateCounter+1了。

今天在下面三段代碼上花費了大量的時間,一直似懂非懂,內心不踏實。

代碼一:

var name = 'window';
var obj = {
    name: 'object',
    getName: function() {
        return this.name;
    }
};
obj.getName(); //object
(obj.getName = obj.getName)(); //window 非嚴格模式下

代碼二:

var name = 'window';
var obj = {
    name: 'object',
    getName: function() {
        return function(){
            return this.name;    
        }
    }
};
obj.getName()(); //window

代碼三:

var name = 'window';
  var obj = {
    name : 'object',
    getName : function(){
      var that = this;
      return function(){
        return that.name;
      };
    }
  };
obj.getName()();    //object

今天在看阮一峯的博客學習Javascript閉包(Closure),對代碼2、代碼三部分非常不解,看到一網友搬出犀牛書(還沒看過,我買了紅寶石書纔看了一點點)裏的話,實在不解什麼是做爲函數調用,什麼是做爲方法調用;

《Javascript權威指南》上說:若是嵌套函數做爲函數調用,其this值不是全局對象(非嚴格模式下)就是undefined(嚴格模式下); 若是嵌套函數做爲方法調用,其this值指向調用它的對象。

又有一位網友說

每一個函數在被調用時,其活動對象都會自動取得兩個特殊變量:this和arguments。內部函數在搜索這個變量時,只會搜索到其活動對象爲止,所以永遠不可能直接訪問外部函數中的這兩個變量(這一點經過前面的圖能夠看得更清楚)。意思就是說找到匿名函數中的this和arguments就不會再往下找了(這裏的往下指的是外層的包含函數,和最外層的window全局環境),而匿名函數的this對象一般指向window,因此輸出的是全局的那個字符串。不過,把外部做用域中的this對象保存在一個閉包可以訪問到的變量裏,就可讓閉包訪問該對象了。

看到這裏大概明白匿名函數的做用域是全局,繼續翻看下面評論,大概意思是說「把this保存在obj做用域下的一個變量中,this就在當前函數的做用域下了」。直到看完也是,似懂非懂,反正就是感受哪裏不對勁,但也說不上了。

直到看到【JavaScript】【函數】閉包閉包!這篇文章的代碼一部分,終於明白其中的邏輯了。

下面就來分析其中的邏輯,我分析的方法就是把不懂的地方一個個用console打印出來

代碼二和代碼一的區別是多了一層嵌套函數,this值就不同了。
ps:我一開始覺得在代碼二中再嵌套一層函數,就會打印出object =_=|||

先來看代碼一,明白以後,另外兩段代碼天然就懂了。

爲何obj.getName()打印出來的是object,由於這時getName方法是在obj的做用域下,因此this指向obj,返回值固然就是object了。

接着看(obj.getName = obj.getName)()刪掉右邊後,打印出的結果變成了object,這就納悶了。
ps:第一眼看上去,這啥玩意,把本身賦值給本身?這不是畫蛇添足,直接用不就好了!

console.log打印出obj.getName後,終於撥雲見天,obj.getName = obj.getName這句話的意思就是把getName函數賦值給本身,這個時候就不是obj.getName,而是getName匿名函數了,匿名函數一般用的方法是()()當即執行,此時再看匿名函數已經脫離obj了,固然this也就指向了全局,打印出window

再來看代碼二,用console打印出obj.getName()會發現是一個匿名函數,而匿名函數的this一般會指向全局,因此也就不難理解了

理解上面兩段代碼,代碼三也就很好理解了。

閉包中引用循環變量

廖雪峯的閉包在文中就很形象的講解了函數中的引用會變化的變量會有什麼後果,我節選了他的結論和代碼。

返回閉包時牢記的一點就是: 返回函數不要引用任何循環變量,或者後續會發生變化的變量。
若是必定要引用循環變量怎麼辦?方法是 再建立一個函數,用該函數的參數綁定循環變量當前的值,不管該循環變量後續如何更改,已綁定到函數參數的值不變:
function count() {
    var arr = [];
    for (var i=1; i<=3; i++) {
        arr.push((function (n) {
            return function () {
                return n * n;
            }
        })(i));
    }
    return arr;
}

var results = count();
var f1 = results[0];
var f2 = results[1];
var f3 = results[2];

f1(); // 1
f2(); // 4
f3(); // 9

這裏的核心就是當即執行,若是不是當即執行的話,變量i就是for循環結束後的值了。

相關文章
相關標籤/搜索