閉包定義:指擁有多個變量和綁定了這些變量的環境的表達式(一般是一個函數),於是這些變量也是該表達式的一部分。
函數內部能夠直接讀取全局變量。
函數內部變量沒法在函數外部訪問。
函數內部聲明要用var或者let聲明,否則會變成全局變量
鏈式做用域:子對象會一級級向上尋找父對象的變量,父對象的變量子對象都是可見的,反之則不行。
在一個閉包環境內修改變量值,不會影響另外一個閉包中的變量。
普通的函數內嵌,內部函數是先執行;而閉包則是:先把內部函數賦給外部函數,而後在執行。javascript
下面這段代碼就是一根典型的閉包html
function f1(){ var a = 10; function f2(){ alert(a); } f2(); //① } f1(); //10 ②
f1
和f1()
的區別不加括號是代碼,加()
是執行這段代碼,加return
是返回一個值,能夠把返回的值賦值給變量,不加return
默認返回undefined
;java
因此①
處有三種寫法: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.increment
時privateCounter
被+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
循環結束後的值了。