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


關於閉包的用法,幾乎是所有前端面試中必點的菜之一,也是考察javascript掌握程度的重要知識之一,下面這題,是某知名IT企業出的題型,我稍加修改,分享如下:

var name = 'global';

var obj = {
    name : 'obj',
    dose : function(){
        this.name = 'dose';
        return function(){
            return this.name;
        }
    }
}

alert(obj.dose().call(this))

請寫出執行結果?

關於這樣的題型,應當怎樣去分析呢?

obj.dose().call(this) 這個表達式有點長,看着有點眼暈,不妨進行一個等價變形。

var xxx = obj.dose();
xxx.call(this);

這樣就清晰多了。這樣一眼就看出是在考察call的用法和this的指向。稍有點基礎的,一眼就可以看出此處的this就是window對象。如果你看不出,就再去看看那本紅寶書3

即使知道此處的this是window還沒完。還要順路普及一下call的用途:

// 1. 替換函數運行環境中的this 
// 2. 傳遞參數
// 3. 運行函數

通過前面的分析可以知道xxx是這樣一個函數:

       function(){
            return this.name;
        }

由於call指定了this是window,所以return this.name 就是 window中的name,即global;

如果是面試呢,這題到此就結束了,不過舉一反三才是我的目的。因此,下面我稍改一下題目:

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)
     }

所以,不要認為閉包有缺點就不敢用,也不要因為閉包的優點而濫用。 是否選擇閉包,視我們的需要來定。

總結一點:學東西不可淺嘗則止,一定要深入原理,舉一反三,利用它的優點,規避它的缺點。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM