再談閉包-詞法做用域

最近打算換工做,因此參加了幾回面試(國內比較知名的幾家互聯網公司)。在面試的過程當中每當被問起閉包,我都會說閉包是做用域的問題?使人驚訝的是幾乎無一例外的當我提到做用域時我都被打斷,並提醒我好好的找一本javascript的書籍看看。而當我忍不住去問面試官對於閉包你是怎麼理解的?我獲得的大可能是回答都是經過返回一個函數,而後經過這個函數來訪問局部變量(私有變量),有的還會扯上聲明提早,this指向等。聽到這些,心理默默滴血,沒錯,我只是菜鳥。javascript

看到有這麼多人不理解閉包,因此我不得再也不次的說起,若是有誤,歡迎指正。html

閉包只是爲了實現詞法做用域而用到的一種數據結構而已

先從阮一峯09年寫的一篇關於閉包的文章開始(原文地址)文中說"能夠把閉包理解爲就是可以讀取其餘函數內部變量的函數,由於js中,只有函數內部的子函數才能讀取局部變量,所以也能夠把閉包定義在一個函數內部的函數。因此閉包本質就是將函數內部和函數外部鏈接起來的一座橋樑"。java

畢竟不是專業學習js的,也不是程序語言方面的專家,在這裏就不去計較了,下文會給出更完整的閉包說明。(PS:我的仍是比較欣賞阮一峯寫的文章的,能將一些概念講得通俗易懂)python

最後還留下了一個思考題面試

  • 示例01:ruby

      var name = "The Window";
      var object = {
        name : "My Object",
        getNameFunc : function(){
          return function(){
            return this.name;
          };
        }
      };
      alert(object.getNameFunc()());
  • 示例02:數據結構

      var name = "The Window";
      var object = {
        name : "My Object",
        getNameFunc : function(){
          var that = this;
          return function(){
            return that.name;
          };
        }
      };
      alert(object.getNameFunc()());

看完這道題,就但願你們將this和閉包分開,不要給本身找麻煩?閉包

在開始說閉包以前,須要理解好如下的概念:函數

  • 詞法做用域(靜態做用域)學習

  • 函數上下文

以前簡單的提過,詞法做用域簡單的理解就是函數的上下文是在聲明是肯定的,而不是在調用時肯定的。這裏不想對這兩個名詞做過多的解釋,咱們知道js/ruby/python等主流的語言都是詞法做用域就好,由於與之相對的動態做用域有許多的問題,因此如今的語言基本都是詞法做用域的。

函數上下文就簡單的理解爲函數執行的環境好了,在這個環境中保存了函數執行所需的變量。

JavaScript權威指南第六版關於閉包的說明

JavaScript採用詞法做用域,也就是說函數的執行依賴於變量的做用域,這個做用域是在函數定義時決定的,而不是函數調用時決定的。爲了實現詞法做用域,JavaScript函數對象的內部狀態不只包含函數的代碼邏輯,還必須引用當前的做用域鏈。函數對象能夠經過做用域鏈相互關聯起來,函數體內部的變量均可以保存在函數做用域內,這種特性在計算機科學文獻中稱爲"閉包"。

當定義一個函數時,它實際上保存了一個做用域鏈。當調用這個函數時,它建立

一個新的對象來存儲它的局部變量,並將這個對象添加到保存的做用域鏈上。

(閉包能夠簡單的理解爲函數用來存儲它的局部變量的對象,這個對象咱們來講是不可見的,是js解釋器實現的過程當中纔會用到的。每個函數都會有這樣的一個對象,做用域鏈則是這些對象之間的關係。)

"閉包"這個術語的來源:指函數變量能夠被隱藏於做用域鏈以內,所以看起來是函數將變量"包裹"了起來。

看文字可能會比較繞,下面是書中的一個例子:

var scope = "global scope";            /*全局變量*/
function checkscope(){
    var scope = "local scope";        /*局部變量*/
    function f(){return scope;}
    return f();
}
checkscope();                        /*=>"local scope"*/
var scope = "global scope";            /*全局變量*/
function checkscope(){
    var scope = "local scope";        /*局部變量*/
    function f(){return scope;}
    return f;
}
checkscope()();                        /*=>"local scope"*/

checkscope最後的返回值都是同樣的,即"local scope"。

上面兩個例子的不一樣之處就是函數f執行的地方不一樣,一個在checkscope這個做用域裏調用的(每個函數都是一個做用域),一個在全局做用域裏調用的。回憶以前說的,函數調用時的上下文或者說做用域是在聲明時肯定的,因此與調用的位置無關,即f函數的調用位置雖然不一樣,調用的環境雖然不一樣,但最終的結果都是同樣的。

說到這裏,閉包就說得差很少了,回過頭來看一下常規的對於閉包的理解:

經過返回函數的形式取得函數的局部變量。

這種說法自己沒有錯,但它只是閉包的一種表現形式,

好比將上例進行下更改:

var scope = "global scope";            /*全局變量*/
function checkscope(fn){
    var scope = "local scope";        /*局部變量*/
    function f(){return scope;}
    fn(f);
}
checkscope(function(func){
    var scope = "func scope";
    return func();
});                                    /*=>"local scope"*/

改爲相似回調的執行方式,結果仍是同樣的,注意結果並非func scope,可是並無返回f函數這一說,難道這就不是閉包了嗎?(固然這裏有點扣字眼)

說說this

想起最開始時的那個思考題了嗎?與閉包就沒什麼關係(注:任何一個函數其實都用到了閉包,但咱們暫且考慮兩層以及兩層以上的嵌套狀況,未嵌套狀況下由於使用的都是全局做用域,結果應該是很直觀的)。this通常用來表明函數的調用對象,它和上下文對象並非同一個,上下文對象對咱們來講是不可見的,除了全局做用域。

  • 示例01:

      var name = "The Window";
      var object = {
        name : "My Object",
        getNameFunc : function(){
          return function(){
            return this.name;
          };
        }
      };
      alert(object.getNameFunc()());        /*The Window*/
  • 示例02:

      var name = "The Window";
      var object = {
        name : "My Object",
        getNameFunc : function(){
          var that = this;
          return function(){
            return that.name;
          };
        }
      };
       var that = {name: 'xiaofu'};            /*干擾項*/ 
      alert(object.getNameFunc()());        /*My Object*/

咱們再來看一下題目,示例01輸出的是"The Window",示例02輸出的是"My Object"。

說明:

  • 示例01最終執行的是 function(){return this.name},由於沒有顯示指明調用對象,因此其this執行全局對象。

  • 示例02先調用object.getNameFunc(),由於顯示的指定了調用對象,因此內部的this是object(注:這裏說的是this指向的問題,尚未說閉包),接着執行function (){return that.name},這個函數在getNameFunc這個函數做用域中聲明的,因此它調用的時候使用的是這個做用域,即獲得var that = this;的這個that;而不是外面的that。

做用域鏈不等同於原型鏈

真不知道這兩個不相關的東西怎麼會被等同起來看待,之後誰告訴我它們是同一個東西,我就想問了,ruby、python這種沒有原型概念的語言難道就沒有做用域鏈了嗎?

更有甚者將變量聲明提高和閉包混在一塊兒,也是醉了。

總結

  • 閉包:函數執行時變量的獲取從聲明的做用域處去獲取(注意鏈式關係,當前沒有就往父級找,知道全局做用域)

  • this:顯示指定調用者則this就指向誰,如未指定則爲全局對象

相關文章
相關標籤/搜索