理解JavaScript中的做用域和做用域鏈

做用域

做用域就是變量與函數的可訪問範圍,即做用域控制着變量與函數的可見性和生命週期。
在JavaScript中,變量的做用域有全局做用域和局部做用域兩種。前端

做用域鏈

函數對象有一個內部屬性[[Scope]],包含了函數被建立後的做用域中對象的集合,
這個集合被稱爲函數的做用域鏈,它決定了哪些數據能被函數訪問。
示例:
當一個函數建立後,它的做用域鏈會被建立此函數的做用域中可訪問的數據對象填充。java

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

在函數add建立時,它的做用域鏈中會填入一個全局對象,該全局對象包含了全部全局變量,以下圖所示(注意:圖片只列舉了所有變量中的一部分):
js做用域鏈_01
執行add函數時會建立一個稱爲「運行期上下文(execution context)」的內部對象,運行期上下文定義了函數執行時的環境。
每一個運行期上下文都有本身的做用域鏈,用於標識符解析。
當運行期上下文被建立時,而它的做用域鏈初始化爲當前運行函數的[[Scope]]所包含的對象,這些值按照它們出如今函數中的順序被複制到運行期上下文的做用域鏈中。
它們共同組成了一個新的對象,叫「活動對象(activation object)」,該對象包含了函數的全部局部變量、命名參數、參數集合以及this,而後此對象會被推入做用域鏈的前端,當運行期上下文被銷燬,活動對象也隨之銷燬。新的做用域鏈以下圖所示:
js做用域鏈_02
在函數執行過程當中,每遇到一個變量,都會經歷一次標識符解析過程以決定從哪裏獲取和存儲數據。該過程從做用域鏈頭部,也就是從活動對象開始搜索,查找同名的標識符,若是找到了就使用這個標識符對應的變量,若是沒找到繼續搜索做用域鏈中的下一個對象,若是搜索完全部對象都未找到,則認爲該標識符未定義。函數執行過程當中,每一個標識符都要經歷這樣的搜索過程。c++

做用域鏈和代碼優化

從做用域鏈的結構能夠看出,在運行期上下文的做用域鏈中,標識符所在的位置越深,讀寫速度就會越慢。
如上圖所示,由於全局變量老是存在於運行期上下文做用域鏈的最末端,所以在標識符解析的時候,查找全局變量是最慢的。因此,在編寫代碼的時候應儘可能少使用全局變量,儘量使用局部變量。一個好的經驗法則是:若是一個跨做用域的對象被引用了一次以上,則先把它存儲到局部變量裏再使用。緩存

改變做用域鏈

函數每次執行時對應的運行期上下文都是獨一無二的,因此屢次調用同一個函數就會致使建立多個運行期上下文,當函數執行完畢,執行上下文會被銷燬。每個運行期上下文都和一個做用域鏈關聯。通常狀況下,在運行期上下文運行的過程當中,其做用域鏈只會被 with 語句和 catch 語句影響。
with語句是對象的快捷應用方式,用來避免書寫重複代碼。
對with語句來講,會將指定的對象添加到做用域鏈中,對catch語句來講,會建立一個新的變量對象,其中包含的是被拋出的錯誤對象的聲明。
此時,做用域鏈中函數的全部局部變量所在的做用域對象會被推後,訪問代價變高了。
在實際應用中,應少用with,把catch中的錯誤委託給一個函數處理。函數

沒有塊級做用域

if(true){
    var i = 0;
    i++;
  }
  console.log(i); //1

若是在c、c++或java語言中,if語句執行完畢後i會被銷燬,而在js中,if語句中的變量聲明是添加到了當前函數的執行環境中,因此在if語句以後仍然能夠正常訪問。優化

模仿塊級做用域

(function(){
  //這裏是塊級做用域
})();

將函數聲明包含在一對圓括號中,表示它其實是一個函數表達式,而緊隨其後的另外一對圓括號會當即調用這個函數。this

小結

  • 做用域就是變量和函數的可訪問範圍,一般,局部環境中的變量和函數是不能被外部環境訪問的;spa

  • 做用域鏈決定了哪些數據可以被當前函數訪問以及訪問的順序;code

  • 函數建立時,會建立一個Global Object,填入它的做用域鏈;函數執行時,會建立一個運行期上下文的對象,它定義了函數執行時的環境。函數執行環境包含一個活動對象,該對象包含了函數的全部局部變量、命名參數、參數集合以及this,它會被推入做用域鏈的最前端;對象

  • 函數執行過程,每遇到一個變量,都會經歷一次標識符解析的過程(逐級向上搜索做用域鏈)以決定從哪裏獲取和存儲數據;

  • 全局變量存在於運行期上下文做用域鏈的最末端,查找最慢,因此咱們應該儘量少使用全局變量,若是使用,就先用局部變量緩存下來;

  • 在運行期上下文運行的過程當中,其做用域鏈只會被 with 語句和 catch 語句影響,應少用with,把catch中的錯誤委託給一個函數處理;

  • js中沒有塊級做用域,可是咱們能夠模仿實現它。

相關文章
相關標籤/搜索