關於閉包的用法,幾乎是全部前端面試中必點的菜之一,也是考察javascript掌握程度的重要知識之一,下面這題,是某知名IT企業出的題型,我稍加修改,分享以下:javascript
var name = 'global'; var obj = { name : 'obj', dose : function(){ this.name = 'dose'; return function(){ return this.name; } } }
alert(obj.dose().call(this))
請寫出執行結果?html
關於這樣的題型,應當怎樣去分析呢?前端
obj.dose().call(this) 這個表達式有點長,看着有點眼暈,不妨進行一個等價變形。java
var xxx = obj.dose(); xxx.call(this);
這樣就清晰多了。這樣一眼就看出是在考察call的用法和this的指向。稍有點基礎的,一眼就能夠看出此處的this就是window對象。若是你看不出,就再去看看那本紅寶書3jquery
即便知道此處的this是window還沒完。還要順路普及一下call的用途:面試
// 1. 替換函數運行環境中的this
// 2. 傳遞參數
// 3. 運行函數瀏覽器
經過前面的分析能夠知道xxx是這樣一個函數:緩存
function(){ return this.name; }
因爲call指定了this是window,因此return this.name 就是 window中的name,即global;閉包
若是是面試呢,這題到此就結束了,不過觸類旁通才是個人目的。所以,下面我稍改一下題目:app
var name = 'global'; var obj = { name : 'obj', dose : function(){ this.name = 'dose'; return function(){ return this.name; }.bind(this) } } alert(obj.dose().call(this))
因爲return的function中用了bind,因此至關於固定了this,外邊再call什麼進來,也只是礙眼法而已。因爲函數內部邦定了this,因此此處的狀況要另外分析了
首先,obj對象定義了name屬性爲'obj';接着在dose 方法內,又改寫了name屬性爲'dose'; 根據做用域鏈的就近原則,alert訪問的確定就是'dose'這個值了。
然而ie派認爲在return中用bind不常見,兼容性也不高。那不妨再變一下:
var name = 'global'; var obj = { name : 'obj', dose : function(){ var that = this; this.name = 'dose'; return function(){ return that.name; } } } alert(obj.dose().call(this))
這種寫法,天然你們都比較認同了。考察仍是相同的內容,只不過是邦定this的手法不同而已。與其說是考察閉包,不如說是考察對基礎知識的理解,由於bind,call,apply之類的方法都是平時使用頻率很高的,對它們多花點時間琢磨一下,必然是有好處的。
最後呢再分享一個面試的趣事。面試官問我,用閉包有什麼好處?
1. 延長做用域鏈。
這一點你們是熟知的,由於閉包函數能夠訪問外層函數做用域中的變量及對象,以代碼來演示一下:
function wrap () { var out = '外部變量'; return function (){ //這裏能夠訪問外部函數中的變量 //實際上就是建立了一個閉包函數 alert(out); } } var inner = wrap(); //雖然wrap運行完畢了,可是inner依然能夠訪問它所建立的做用域中的變量 //這就是閉包第一個用法 inner();
2. 生成預編譯函數。
這一點是借用jquery中的說法,實際上就是經過閉包把外層函數提供的參數保存起來,在閉包運行的時候就能夠獲得預先指定的參數
var fn = []; for(var i = 0;i<3;i++){ (function(n){ fn.push(function(){ alert(n) }) })(i) }
說化函數的curry化,可能知道的人會更多一點,和上面的例子類似,不過它強調的是參數的積累。
function addGenerator(num){ return function(toAdd){ return num + toAdd; }; } //建立一個新的函數 var addFive = addGenerator(5); alert(addFive(4)==9) //true
3.更好的組織代碼,好比模塊化,異步代碼轉同步等。
Deferred.next(function(){ alert(1) return Deferred.wait(3) }).next(function(){ alert(2) }).next(function(){ alert(3); });
4. 處理異步形成的變量不能即時傳遞的問題
/** * html結構: * <ul> * <li> 0</li> * ...... * <li> 9 </li> * </ul> */ //點擊彈出對應的數字 var items = document.querySelectorAll('li'); for(var i=0;i<items.length;i++){ items[i].onclick = function(){ alert(i) } } //上面的程序結果是:每次都彈出10; //爲了在用戶點擊的時候,能彈出對應的數字 // 須要構建一個閉包,將參數緩存起來 for(var i=0;i<items.length;i++){ items[i].onclick = (function(n){ return function(){ alert(n) } })(i) } // 這時點擊的時候就會彈出邦定的數字了,強烈推薦試一下
暫時就想到這些,其它的想到再補充。
他接着又問我,那用閉包又有什麼壞處?
1. 增長了內存的消耗。
2. 某些瀏覽器上由於回收機制的問題,有內存溢出風險。
3. 增長了代碼的複雜度,維護和調試不便。
我balabala說了一些,他就笑了。說你這一邊是矛,一邊是盾,究竟是矛好呢仍是盾好呢?當時也沒想這麼多,反射性的回答說,看狀況選用咯。他說這樣是不行的。
原來挖了個坑在這裏等着我呢,真是太不厚道了。凡事都有兩面性嘛,只要撐握的好,天然是能夠避害用利。咱們都知道電是很危險,也頗有用,只要掌握了它的特性,就能很好的利用,而不是受其害,因此並非矛盾的就不可取。拿最後一個例子來講,若是不用閉包,難道就沒有辦法了嗎?顯然不是的,好比下面的代碼就不用閉包,照樣解決該問題:
function fn(n){ items[n].onclick = function(){ alert(n) } } for(var i=0;i<items.length;i++){ fn(i) }
因此,不要認爲閉包有缺點就不敢用,也不要由於閉包的優勢而濫用。 是否選擇閉包,視咱們的須要來定。
總結一點:學東西不可淺嘗則止,必定要深刻原理,觸類旁通,利用它的優勢,規避它的缺點。