Java中的作用域是js中比較重要的一部分,也是大多數面試中必考的內容,我們有必要更加深入的了解下js中作用域。
看一個栗子
仔細閱讀以下Java代碼,你覺得運行結果會是什么呢?是 1 還是2?

不是1,也不是2,答案卻是是undefined.
為什么會產生這個讓人意外的結果呢?我們得來看下js中的預解析。
Java預解析
Java在瀏覽器中運行的過程分為兩個階段預解析階段 執行階段,在Java引擎對Java代碼進行執行之前,需要進行預先處理,然后再對處理后的代碼進行執行。
我們平時書寫的Java代碼并不是Java執行的代碼(V8引擎讀取一行執行一行這種理解是錯誤的),它需要預解釋后,再由引擎進行執行.
具體的解釋過程涉及到瀏覽器內核的技術不屬于前端領域,不過我們可以淺顯的理解一下V8在處理Java的一般過程:
以上例中的var a = 2;為例,我們一般人的理解為聲明了一個值為2的變量a,但是在Java引擎處理時卻分為了兩個步驟:
1. 讀取var a后,在當前作用域中查找是否有相同聲明,如果沒有就在當前作用域集合中創建一個名為a的變量,否則忽略此聲明繼續進行解析.
2. 接下來,V8引擎會處理a = 2的賦值操作,首先會詢問當前作用域中是否有名為a的變量,如果有進行賦值,否則繼續向上級作用域詢問.
Java執行環境
我們上面提到的所謂java預解釋正是創建函數的執行環境(又稱“執行上下文”),只有搞定了java的執行環境我們才能搞清楚一段代碼在執行過后為什么產生這樣的結果。
我們用一段偽代碼表示創立的執行環境

作用域鏈(scopeChain)包括下面提到的變量對象(variableObject)和所有父級執行上下文中的變量對象.
變量對象(variableObject)是與執行上下文相關的數據作用域,一個與上下文相關的特殊對象,其中存儲了在上下文中定義的變量和函數聲明:
· 變量
· 函數聲明
· 函數的形參
在有了這些基板概念之后我們可以梳理一下js引擎創建執行的過程:
· 創建階段
· 創建Scope chain
· 創建variableObject
· 設置this
· 執行階段
· 變量的值、函數的引用
· 執行代碼
而變量對象的創建細節如下:
· 根據函數的參數,創建并初始化arguments object
· 掃描函數內部代碼,查找函數聲明(Function declaration)
· 對于所有找到的函數聲明,將函數名和函數引用存入變量對象中
· 如果變量對象中已經有同名的函數,那么就進行覆蓋
· 掃描函數內部代碼,查找變量聲明(Variable declaration)
· 對于所有找到的變量聲明,將變量名存入變量對象中,并初始化為"undefined"
· 如果變量名稱跟已經聲明的形式參數或函數相同,則變量聲明不會干擾已經存在的這類屬性
變量提升
正是由于以上的處理,產生了大家熟知的Java中的變量提升,具體以上代碼的執行過程如以下偽代碼所示:



我們可以明顯看到,a變量在預解釋階段已經被賦值undefined,在執行階段js是自上而下單線執行,當console.log(a)執行之時,a=2還沒有被執行,a變量的值便是預處理階段被賦予的undefined,
函數聲明與函數表達式
我們看到,在編譯器處理階段,除了被var聲明的變量會有變量提升這一特性之外,函數也會產生這一特性,但是函數聲明與函數表達式兩種范式創建的函數卻表現出不同的結果.
我們先看一個實例,運行以下代碼

f成功被打印出來,而g函數出現了類型錯誤,這是什么原因呢?

我們看到,在預解釋階段函數聲明的f是被指向了正確的函數得以執行,而函數表達式g被賦予undefined,undefined無法被當作函數執行因此報錯g is not a function.
沖突處理
通常情況下我們不會將同一變量變量重復聲明,但是出現了類似情況后,編譯器會如何處理這些沖突呢?
1. 變量之間沖突
執行以下函數:

結果顯而易見,后聲明變量值覆蓋前者的值
函數之間沖突

結果同變量沖突,后者覆蓋前者.
2. 函數與變量之間沖突

結果如下,函數聲明將覆蓋變量聲明
[Function: f]
ES6中的let
在ES6中出現了兩個最新的聲明語法let與const,我們以let為例,進行測試看看與var的區別.

這段代碼直接報錯顯示未定義,let與const擁有類似的特性,阻止了變量提升,當代碼執行到console.log(a)時,執行換將中a還從未被定義,因此產生了錯誤.返回。