對於變數的作用域(scope),C、Java等語言採取的是「block scope」的方式。與之不同,JavaScript所採取的是「function scope」的方式 — 變數的作用域僅由所處的function決定,與if、for等邏輯塊無關。例如,以下這個例子展示了JavaScript中與C、Java等語言不一樣的行為:
在C、Java等「block scope」的語言中,if語句、for語句等邏輯區塊結束後,在這些邏輯區塊內部定義的變數將會被銷毀。 JavaScript與之不同,只要一個變數定義在某function內,那麼整個function內的所有程式碼均可存取到該變量,即使這些程式碼在變數定義之前:
在上述例子中,如果function中a從未被定義,那麼console.log(a)將拋出ReferenceError。當function中對a進行定義後,即使這個定義在a變數呼叫語句之後,對a的呼叫也屬於合法運算(如果對a變數的定義發生在呼叫語句之後,那麼呼叫語句中a變數的值為undefined )。事實上,在function內用var關鍵字進行定義的所有變量,其定義操作都會被提至function的開頭(賦值操作依然留在var定義的那一行),這在JavaScript中稱之為hoisting。例如,上述程式碼就等價於:
變數的作用域鏈
聯繫JavaScript中變數的儲存,可以很好的理解JS中的「function scope」與hoisting。由於變數是儲存在全域物件或函數呼叫物件上的,因此當在function中定義變數時,無論這個變數定義在function的什麼地方,這次function呼叫所使用的函數呼叫物件中必然會出現一個與此變數同名的屬性。如此一來,function中的任何地方都可以存取到該變數。
涉及函數調用,JavaScript中還有一個更有趣的概念:變數的作用域鏈— 由於變數是儲存在全域物件或函數調用物件上的,因此在存取變數時,可以從多個物件上獲取值。以下面的程式碼為例:
在上述程式碼中2級函數(level-2 function)的內部,當試圖存取x變數時,程式可以從3個物件上搜尋對應的屬性值:呼叫2級函數所使用的函數呼叫物件、呼叫1級函數所使用的函數呼叫物件、全域物件— 根據函數定義的巢狀關係,JavaScript將產生一個由全域物件和函數呼叫物件所組成的物件鏈。存取變數時,程式將從離訪問語句最近的那個物件開始搜索,如果沒有搜索到,則在物件鏈中上一層的物件中繼續進行搜索,直至全域物件。
由於這個物件鏈與變數的作用域有關,因此也叫做「作用域鏈」。
如果需要暫時改變作用域鏈,將某個物件插入到作用域鏈的最前端(作為最先存取到的那個函數物件),可以使用with語句:
不過,在JavaScript嚴格模式下,with語句是被禁用的;即使在非嚴格模式下,也不建議使用with語句。