函數做用域鏈

  基本任何變成語言都有做用域的概念,即各類變量的可見性和生命週期,通俗來講,就是變量在什麼地方能夠被調用,什麼地方不能夠被調用。此處是js的函數做用域鏈的概念理解。javascript

  一、全局做用域, 局部做用域html

    全局做用域:處於全局做用域的變量爲全局變量,在代碼中的任何地方均可被可視,即在任何地方均可被調用。前端

    常見狀況有如下幾種:java

     (1)最外層函數和最外層定義的變量擁有全局做用域。函數

     (2)全部未聲明而直接賦值的變量擁有全局做用域。性能

     (3)全部window對象擁有全局做用域。優化

   2.局部做用域:this

    僅在必定的代碼段,纔可見(纔可被調用),常見的是函數內部聲明的變量。spa

var a = 1;
function fun1(){
    var b = 2;
      mn = 123;
    function fun2(){
        var c = 3;          
    }    
}

/*
  a: 全局,最外層函數以外定義的變量。
  fun1:全局,最外層函數
  b:局部,函數內定義
  mn:全局,未聲明而直接賦值,爲window對象屬性,屬於全局做用域
  fun2:局部,在函數內聲明的函數
  c:局部,函數內聲明
*/

    3.做用域鏈:code

    

 在JavaScript中,函數也是對象,實際上,JavaScript裏一切都是對象。函數對象和其它對象同樣,擁有能夠經過代碼訪問的屬性和一系列僅供JavaScript引擎訪問的內部屬性。其中一個內部屬性是[[Scope]],由ECMA-262標準第三版定義,該內部屬性包含了函數被建立的做用域中對象的集合,這個集合被稱爲函數的做用域鏈,它決定了哪些數據能被函數訪問。

  當一個函數建立後,它的做用域鏈會被建立此函數的做用域中可訪問的數據對象填充。例如定義下面這樣一個函數:

function add(num1,num2) {
    var sum = num1 + num2;
    return sum;
}

 

  在函數add建立時,它的做用域鏈中會填入一個全局對象,該全局對象包含了全部全局變量,以下圖所示(注意:圖片只例舉了所有變量中的一部分):

 

JavaScript做用域鏈

 

  函數add的做用域將會在執行時用到。例如執行以下代碼:

var total = add(5,10);

 

  執行此函數時會建立一個稱爲「運行期上下文(execution context)」的內部對象,運行期上下文定義了函數執行時的環境。每一個運行期上下文都有本身的做用域鏈,用於標識符解析,當運行期上下文被建立時,而它的做用域鏈初始化爲當前運行函數的[[Scope]]所包含的對象。

  這些值按照它們出如今函數中的順序被複制到運行期上下文的做用域鏈中。它們共同組成了一個新的對象,叫「活動對象(activation object)」,該對象包含了函數的全部局部變量、命名參數、參數集合以及this,而後此對象會被推入做用域鏈的前端,當運行期上下文被銷燬,活動對象也隨之銷燬。新的做用域鏈以下圖所示:

 

JavaScript做用域鏈

 

  在函數執行過程當中,沒遇到一個變量,都會經歷一次標識符解析過程以決定從哪裏獲取和存儲數據。該過程從做用域鏈頭部,也就是從活動對象開始搜索,查找同名的標識符,若是找到了就使用這個標識符對應的變量,若是沒找到繼續搜索做用域鏈中的下一個對象,若是搜索完全部對象都未找到,則認爲該標識符未定義。函數執行過程當中,每一個標識符都要經歷這樣的搜索過程。

  簡單總結: 以函數爲例:函數定義時(或被聲明時),此時會生成一個全局執行期上下文,函數每次執行都會產生一個本身惟一的執行期上下文,而後和以前函數定義產生的執行期上下文造成棧式做用域鏈,全局位於棧底,而剛生成上下文位於棧頂,訪問時,優先訪問棧頂執行期上下文,若沒有目標,再往下尋找,依次規則,通常全局變量都是被最後訪問的,因此儘可能少定義全局變量。

  4.做用域的代碼優化:

  訪問變量在做用域鏈的位置越深,訪問之越耗時,因此儘可能少的定義全局變量,解決方法:若是一個跨做用域的對象被引用了一次以上,則先把它存儲到局部變量裏再使用

  

function changeColor(){
    var doc=document; /*用一個局部變量儲存全局的document,從而提升訪問效率,當該種狀況重複次數多時效果會很明顯*/
    doc.getElementById("btnChange").onclick=function(){
        doc.getElementById("targetCanvas").style.backgroundColor="red";
    };
}

  5.改變做用域鏈的方法:

    1.with(){ 此處的做用域爲with參數指定的}

      當調用with時會產生一個新的執行期上下文,而後插入到當前做用域鏈的棧頂處,致使結果是僅僅with代碼中某些變量訪問快了,但全部局部做用域被擠到第二個位置,局部做用域的變量訪問變慢,最後的結果是訪問代價更高,因此with儘可能能不用就別用。

    2.catch{},

      try-catch語句中,當try中代碼報錯時會跳到catch塊中,此時會將異常對象放到做用域鏈的棧頂,全部局部做用域被擠到第二個位置,致使性能下降,解決方法:將異常對象做爲形參傳入處理函數便可。

  6.預編譯過程建立的AO對象,就是執行期上下文,聯繫着理解感受挺好。

  預編譯過程:

  

 *JS執行三部曲:

  1.語法分析:

    通篇掃描,校驗低級語法錯誤,即一眼就可看出的錯誤。(eg:括號未補全,引號未補全,變量未聲明就打印等等)

  2.預編譯:

  (1)預編譯前奏:

    a)imply\globle 暗示全局變量 ,任何變量未經聲明就賦值,此變量爲全局對象window全部;

      var a = b = 0; 

    ==>

      var a = 0;

        b = 0;(從右向左)

     連續賦值,優先級是從右向左

    b)一切聲明的全局變量,此變量爲全局對象window全部。

  (2)正式預編譯

    a)說明:

    Activation object = {  

      a: undefined,

      b: function b(){}

     }

    即執行上下文。 

    b)預編譯四部曲:

      1.建立AO對象

      2.找形參 和 變量的聲明,將形參 和 聲明的變量 做爲AO的屬性,並賦值爲undefined;(注:函數聲明不屬於變量的聲明, 賦值操做也不屬於變量的聲明)

      3.將傳遞的形參和實參值統一

      4.在函數體裏找函數聲明,值賦予函數體

    3.解釋執行(即一行一行執行)。

      執行過程當中,若在函數外賦值操做,則進行這樣的操做:

      //例如:a = 100; -> window.a = 100;與AO中同名的變量不衝突,一個局部,一個全局

參考資料:http://www.cnblogs.com/lhb25/archive/2011/09/06/javascript-scope-chain.html

相關文章
相關標籤/搜索