JavaScript之一: 閉包、執行環境、做用域鏈

  這是大蝦的第一篇博文,大蝦試圖用最直白的語言去描述出所理解的東西,大蝦是菜鳥,水平有限,有誤的地方但願路過的朋友們務必指正,謝謝你們了。javascript

  從讀書時代一路走來,大蝦在學習的時候逐漸喜歡上了去追尋根源,這個東西究竟是爲何?他有什麼用處?他解決了什麼問題?他是怎麼被想到的?從這些問題當中,咱們可以學到很是多,大蝦深有體會。我相信,即便是這些東西在發明之時,就算是創始人也未必思考的這麼周全,不少狀況下,它必定是先遇到了什麼實際問題以後,再去思考解決方案。也就是說,每個新知識新東西的提出,必定是爲着解決某個問題而出現的,不然他的存在就沒有意義,而脫離了實際問題的學習也是沒有意義的。在學習這個東西的過程當中,大蝦很看重的是,假如一樣的是遇到了這個問題,大蝦會怎麼作去解決?別人又是怎麼解決的?爲何別人的解決方案這麼優秀?他是怎麼想到的?我怎麼就想不到呢?這種過程使大蝦受益不淺。前端

  話很少說,切入正題。本文主要介紹閉包。相信不少人只知道學閉包,直接去看他的原理,實現過程什麼的,可是根據個人瞭解來看,初學者知道閉包的用途的並很少,爲何須要閉包?這些都不清楚,然而在大蝦看來,這個很是重要,由於它對應着實際問題的解決,理論脫離了實踐將變得毫無心義。java

  話說二十年前,祖師爺創立js的時候,那時候頁面並不複雜,大型的網頁也很少,頁面沒什麼js的時候,人們寫頁面時全局變量是隨便定義的,直到某一天,隨着頁面js的增多,問題來了。拿一個模擬的alert來講,若是這麼寫:閉包

var temp=a;
var abs=function(){};
var yourAlert=function(){};

  那麼在這種狀況下,全局變量temp,abs,yourAlert就被污染了。也就是說,若是要實現另一個功能,好比說按鈕btn,這個時候也須要寫本身的代碼,那麼它的變量起名必須從新起名,必須避開上面的變量,不然就會把上面的內容給覆蓋掉。這些新功能一旦數量不少,那麼你的起名就必須避開全部的已用過的變量名,你必須挨個檢查全部功能的變量名以保證他的不重複,這樣就給開發帶來很大的不方便。因此迫切的須要一種方法來避免它,來保護變量不被篡改污染。再者,在頁面中,常常遇到屢次調用的狀況,一樣以上面的alert爲例,假如用戶觸發了10次alert,若是說每一次的觸發都要從新建立一個alert的話,那樣豈不是特別麻煩,特別消耗內存?這個時候一樣須要一種可以反覆調用的方法來優化代碼的執行性能。 函數

  閉包應運而生。性能

  來看看閉包的實現過程,他究竟是如何實現而且達到上述目的並解決實際問題的呢?深入的理解整個本質的實現過程有助於咱們的開發運用。學習

  當一個函數被調用時,一個執行環境(也稱執行上下文)就會被建立(execution context),然而在js引擎內部,這個執行環境建立過程被分爲了2個階段:優化

    一、  創建階段(此時還並無執行具體的函數體的代碼)this

        創建變量對象(variable object):函數裏面的arguments對象、函數參數、內部變量、函數聲明spa

          一、  創建arguments對象,檢查當前環境下的參數,創建該對象下的屬性和屬性值

          二、  檢查當前環境下的函數聲明:每找到一個函數聲明,就會在變量對象裏用函數名創建一個屬性,屬性值就是指向函數地址的引用。若是該函數名已經存在,那麼其對應的屬性值就會指向新的引用。

          三、  檢查當前環境下的變量聲明:每找到一個變量聲明,就在變量對象下創建一個屬性,其值爲undefined(此時還未賦值)。若是該變量名已經存在,會直接跳過(防止指向函數的屬性值被變量屬性覆蓋爲undefined)

         創建做用域鏈

                當前變量對象被添加到執行環境的前端

         肯定this的值

    二、  代碼執行階段

        執行函數中的代碼,對變量賦值、函數引用、執行其餘代碼等等…

  具體的來看一個函數的代碼:

1 function f(x){
2   var a=20;
3   var b=function(){
4 
5   };    
6   function d(){
7  
8    };
9 }
f(100);

  在調用f(100)的時候,執行環境的創建階段發生以下變化:

fExecutionContext{ // f函數執行環境
  variableObject:{// f函數變量對象(對於函數來講,也稱爲活動對象AO)
    arguments:{
       0:100;
       length:1;
    },
    x:100,
    d:pointer to function d()//指向d函數的引用,實際上保存的是地址,它的順序也在變量聲明之上
     a:undefined,
     b:undefined, 
  },
  scopeChain:{....},//做用域鏈
  this:{...}// this值
}

  當上述創建階段結束,js引擎立馬進入執行階段,一行一行的運行函數代碼,給variableObject的屬性賦值,執行階段完成以下:

 fExecutionContext{ // f函數執行環境
  variableObject:{// f函數變量對象(對於函數來講,也稱爲活動對象AO)
    arguments:{
        0:100;
        length:1;
     },
    x:100,
       d:pointer to function d()//指向d函數的引用,實際上保存的是地址,它(函數聲明)的順序也在變量聲明之上
     a:20,
     b:pointer to function b(), 
  },
   scopeChain:{....},//做用域鏈
  this:{...}// this值
 }            

  事實上,若是這個環境是函數,變量對象並不可以被直接訪問到,此時函數的活動對象(AO)將代替變量對象的角色。咱們能夠看到,上述環境中,執行環境的做用域鏈也被建立完成。

  那做用域鏈又是什麼呢?事實上,在函數的建立時,會預先建立一個包含全局變量對象的做用域鏈。做用域鏈的本質是一個指向變量對象的指針列表!它只是引用而實際上並不包含變量對象!爲了方便理解,能夠把它想象成一根鏈條(實際上並非真的存在這麼一根鏈條),上面依次標記着順序0,1,2,3.......,每個數字對應着一個變量對象。在訪問變量對象中的屬性時,只能按照標記的順序按0,1,2,3...的順序依次訪問,在這個鏈條的最前端,始終是當前執行的代碼所在的環境的變量對象(能夠理解爲順序標記爲0),建立執行環境時,當前函數的活動對象將會被推入到做用域鏈的最前端!而下一個變量對象則來自其外部函數(順序標記爲1),再下一個則是外部函數的外部函數,全局執行環境的變量對象始終是鏈條的最後位置,全局的變量對象始終是最後一個被訪問到。

  在本例中,以函數f爲例,其做用域鏈關係以下圖

 

  在尋找變量名和函數名的時候,會首先在順序爲0的位置的變量對象中尋找(也就是它本身的活動對象),若是找不到,就會向下一級變量對象尋找(函數的外部函數的變量對象,在做用域鏈中順序標記爲1),一直搜索到最後一個對象——全局環境的變量對象爲止。全局環境的變量對象始終存在於每個做用域鏈中!當函數執行完畢後,函數的活動對象就會被銷燬,內存中僅保存全局變量對象。

  然!而!且!慢!閉包的狀況不同啊!

  看下面這個超級簡單的例子。

 function f(){
   var a=20; 
   return function d(){
   a--;
    };
 }
var sB=f(); sB();

  上面的d函數是直接定義在函數f的內部中的,便是說函數f是函數d的外部函數,按照上面所述原則,在函數d的做用域鏈中,函數f的變量對象會被添加進函數d的做用域鏈中!此時函數d的做用域鏈一共有3個對象:函數d的活動對象會被標記爲0,函數f的變量對象會被標記爲1,全局變量對象標記爲2,訪問時按照0,1,2的順序訪問,尋找變量時先在本身的活動對象裏找,再去順序爲2即函數f裏面找,這樣就能訪問外部函數f中的全部變量。然而當函數f執行完畢時,它的執行環境的做用域鏈會被銷燬,但它的活動對象並無被銷燬,仍然保存在內存中,匿名函數d的做用域鏈仍然在引用着這個活動對象,直到匿名函數d被銷燬,函數f的活動對象纔會被銷燬。這就是閉包的最大的不一樣之處!

  咱們來畫個流程圖,理解下閉包過程當中所發生的變化。從函數的建立開始。

  前方高能,多圖預警!請在wifi下點開,土豪請無視。

一、函數f的建立

 

二、函數f開始執行了

三、執行到 return語句

四、執行var sB=f();

五、調用函數d

六、執行完畢

  這就是閉包的整個實現過程,閉包實現後,能夠在全局反覆調用內部函數d(),此時在即便全局定義相同的變量a,調用函數時,使用的值仍然是函數f的活動對象裏面的值,外面的更改沒法影響到局部變量a。這裏只是作個簡單的介紹,閉包還有不少應用狀況,實際狀況也更加複雜,還有很長的路要走。

  參考書籍:《JavaScript高級程序設計第3版》

  參考內容: http://tieba.baidu.com/p/2348703848

        http://blogread.cn/it/article/6178?f=sa 

相關文章
相關標籤/搜索