JavaScript中的做用域是js中比較重要的一部分,也是大多數面試中必考的內容,咱們有必要更加深刻的瞭解下js中做用域。javascript
看一個栗子前端
仔細閱讀如下JavaScript代碼,你以爲運行結果會是什麼呢?是 1 仍是2?java
不是1,也不是2,答案倒是是undefined.面試
爲何會產生這個讓人意外的結果呢?咱們得來看下js中的預解析。瀏覽器
JavaScript預解析函數
JavaScript在瀏覽器中運行的過程分爲兩個階段預解析階段 執行階段,在JavaScript引擎對JavaScript代碼進行執行以前,須要進行預先處理,而後再對處理後的代碼進行執行。測試
咱們平時書寫的JavaScript代碼並非JavaScript執行的代碼(V8引擎讀取一行執行一行這種理解是錯誤的),它須要預解釋後,再由引擎進行執行.this
具體的解釋過程涉及到瀏覽器內核的技術不屬於前端領域,不過咱們能夠淺顯的理解一下V8在處理JavaScript的通常過程:3d
以上例中的var a = 2;爲例,咱們通常人的理解爲聲明瞭一個值爲2的變量a,可是在JavaScript引擎處理時卻分爲了兩個步驟:cdn
1. 讀取var a後,在當前做用域中查找是否有相同聲明,若是沒有就在當前做用域集合中建立一個名爲a的變量,不然忽略此聲明繼續進行解析.
2. 接下來,V8引擎會處理a = 2的賦值操做,首先會詢問當前做用域中是否有名爲a的變量,若是有進行賦值,不然繼續向上級做用域詢問.
JavaScript執行環境
咱們上面提到的所謂javascript預解釋正是建立函數的執行環境(又稱「執行上下文」),只有搞定了javascript的執行環境咱們才能搞清楚一段代碼在執行事後爲何產生這樣的結果。
咱們用一段僞代碼表示創立的執行環境
做用域鏈(scopeChain)包括下面提到的變量對象(variableObject)和全部父級執行上下文中的變量對象.
變量對象(variableObject)是與執行上下文相關的數據做用域,一個與上下文相關的特殊對象,其中存儲了在上下文中定義的變量和函數聲明:
· 變量
· 函數聲明
· 函數的形參
在有了這些基板概念以後咱們能夠梳理一下js引擎建立執行的過程:
· 建立階段
· 建立Scope chain
· 建立variableObject
· 設置this
· 執行階段
· 變量的值、函數的引用
· 執行代碼
而變量對象的建立細節以下:
· 根據函數的參數,建立並初始化arguments object
· 掃描函數內部代碼,查找函數聲明(Function declaration)
· 對於全部找到的函數聲明,將函數名和函數引用存入變量對象中
· 若是變量對象中已經有同名的函數,那麼就進行覆蓋
· 掃描函數內部代碼,查找變量聲明(Variable declaration)
· 對於全部找到的變量聲明,將變量名存入變量對象中,並初始化爲"undefined"
· 若是變量名稱跟已經聲明的形式參數或函數相同,則變量聲明不會干擾已經存在的這類屬性
變量提高
正是因爲以上的處理,產生了你們熟知的JavaScript中的變量提高,具體以上代碼的執行過程如如下僞代碼所示:
咱們能夠明顯看到,a變量在預解釋階段已經被賦值undefined,在執行階段js是自上而下單線執行,當console.log(a)執行之時,a=2尚未被執行,a變量的值即是預處理階段被賦予的undefined,
函數聲明與函數表達式
咱們看到,在編譯器處理階段,除了被var聲明的變量會有變量提高這一特性以外,函數也會產生這一特性,可是函數聲明與函數表達式兩種範式建立的函數卻表現出不一樣的結果.
咱們先看一個實例,運行如下代碼
f成功被打印出來,而g函數出現了類型錯誤,這是什麼緣由呢?
咱們看到,在預解釋階段函數聲明的f是被指向了正確的函數得以執行,而函數表達式g被賦予undefined,undefined沒法被看成函數執行所以報錯g is not a function.
衝突處理
一般狀況下咱們不會將同一變量變量重複聲明,可是出現了相似狀況後,編譯器會如何處理這些衝突呢?
1. 變量之間衝突
執行如下函數:
結果顯而易見,後聲明變量值覆蓋前者的值
1. 函數之間衝突
結果同變量衝突,後者覆蓋前者.
2. 函數與變量之間衝突
結果以下,函數聲明將覆蓋變量聲明
[Function: f]
ES6中的let
在ES6中出現了兩個最新的聲明語法let與const,咱們以let爲例,進行測試看看與var的區別.
這段代碼直接報錯顯示未定義,let與const擁有相似的特性,阻止了變量提高,當代碼執行到console.log(a)時,執行換將中a還從未被定義,所以產生了錯誤.