用一道面試題考察對閉包的理解

關於閉包的用法,幾乎是全部前端面試中必點的菜之一,也是考察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)
     }

因此,不要認爲閉包有缺點就不敢用,也不要由於閉包的優勢而濫用。 是否選擇閉包,視咱們的須要來定。

總結一點:學東西不可淺嘗則止,必定要深刻原理,觸類旁通,利用它的優勢,規避它的缺點。

相關文章
相關標籤/搜索