JavaScript是基於==詞法作用域==的語言:通過閱讀包含變數定義在內的數行源碼就能知道變數的作用域。全局變數在程式中始終都是有定義的。==局部變數在聲明它的函數體內以及其所嵌套的函數內始終是有定義的。== 如果將一個局部變數看做是自定義實現的對象的屬性的話 ,那麼可以換個角度來解讀變數作用域 ...
JavaScript是基於詞法作用域的語言:通過閱讀包含變數定義在內的數行源碼就能知道變數的作用域。全局變數在程式中始終都是有定義的。局部變數在聲明它的函數體內以及其所嵌套的函數內始終是有定義的。
如果將一個局部變數看做是自定義實現的對象的屬性的話,那麼可以換個角度來解讀變數作用域。每一段JavaScript代碼(全局代碼或函數)都有一個與之關聯的作用域鏈(scope chain)。這個作用域鏈是一個對象列表或者鏈表,這組對象定義了這段代碼“作用域中”的變數。當JavaScript需要查找變數x的值的時候(這個過程稱做“變數解析”(variable resolution)),它會從鏈中的第一個對象開始查找,如果這個對象有一個名為x的屬性,則會直接使用這個屬性的值,如果第一個對象中不存在名為x的屬性,JavaScript會繼續查找鏈上的下一個對象。如果第二個對象依然沒有名為x的屬性,則會繼續查找下一個對象,以此類推。如果作用域鏈上沒有任何一個對象含有屬性x,那麼就認為這段代碼的作用域鏈上不存在x,並最終拋出一個引用錯誤(ReferenceError)異常。
在JavaScript的最頂層代碼中(也就是不包含在任何函數定義內的代碼),作用域鏈由一個全局對象組成。在不包含嵌套的函數體內,作用域鏈上有兩個對象,第一個是定義函數參數和局部變數的對象,第二個是全局對象。 在一個嵌套的函數體內,作用域鏈上至少有三個對象。理解對象鏈的創建規則是非常重要的。當定義一個函數時,它實際上保存一個作用域鏈。當調用這個函數時,它創建一個新的對象來存儲它的局部變數,並將這個對象添加至保存的那個作用域鏈上,同時創建一個新的更長的表示函數調用作用域的“鏈”。對於嵌套函數來講,事情變得更加有趣,每次調用外部函數時,內部函數又會重新定義一遍。 因為每次調用外部函數的時候,作用域鏈都是不同的。內部函數在每次定義的時候都有微妙的差別——在每次調用外部函數時,內部函數的代碼都是相同的,而且關聯這段代碼的作用域鏈也不相同。
作用域鏈的概念對於理解with語句(見5.7.1節)是非常有幫助的,同樣對理解閉包(見8.6節)的概念也至關重要。