函數宣告和變數宣告總是被JavaScript解釋器隱含地提升(hoist)到包含他們的作用域的最頂端。以下這篇文章主要為大家介紹了關於JavaScript中Hoisting(變數提升與函數宣告提升)的相關資料,需要的朋友可以參考借鑒,下面來一起看看吧。
本文主要為大家介紹了關於JavaScript中Hoisting(變數提升與函數宣告提升)的相關內容,分享出來供大家參考學習,下面話不多說了,來一起看看詳細的介紹吧。
如何將 函數宣告 / 變數 “移動” 到作用域的頂端。
術語 Hoisting(提升) 在許多 JavaScript 部落格中被用來解釋識別碼的解析。其實 Hoisting(提升) 這個字是用來解釋 變數 和 函數宣告 是如何被提升到 函數或全域 作用域頂端的。你在任何的 JavaScript 文件中找不到這個術語,我們所說的 Hoisting(提升) 只是使用了其字面含義來做個比喻。
如果你已經對 JavaScript 作用域運作原理有基本的了解,那麼更深入的了解 Hoisting(提升) 有助於你建立更強大的基礎知識。 (愚人碼頭註:作為JavaScript 中的一個總要概念,變數提升和函數聲明提升經常在前端開發面試時被問及,或者在前端開發筆試題中出現。可見了解Hoisting(提升) 的重要性。)
為了更好地理解基礎知識,讓我們來回顧一下「Hoisting(提升)」 到底意味著什麼。另外,給你一個提醒,JavaScript 是一種解釋性語言,這不同於編譯性語言,這意味著JS程式碼是逐行執行的。
請考慮以下範例:
console.log(notyetdeclared); // 打印 'undefined' var notyetdeclared = 'now it is declared'; hoisting(); function hoisting(){ console.log(notyetdeclared); // 打印 'undefined' var notyetdeclared = 'declared differently'; console.log(notyetdeclared); // 打印 'declared differently' }
在分析上面的範例程式碼之後,提出幾個問題:
第6 行,該函數宣告之前為何能存取?
第 1 行,沒有拋出錯誤,是因為這時變數 notyetdeclared 不存在嗎?
第 4 行,notyetdeclared 已經在全域作用域內宣告了,為什麼在第 9 行列印時還是 undefined 呢?
JavaScript 是非常合乎邏輯的,所有這些奇怪問題都有一個明確的解釋。
我們從頂部開始解釋,當程式碼在 JavaScript 中執行時,就會建立一個執行期上下文。 JavaScript 中有兩種主要的執行期上下文類型– 全域執行期上下文和函數執行期上下文(愚人碼頭註:特別注意,執行期上下文和我們平常說的上下文不同,執行期上下文指的是作用域,而平常說的上下文是this 的取值指向)。由於 JavaScript 是基於單執行緒執行模型,所以每次只能執行一段程式碼。
對於我們上面的程式碼,這個過程如圖所示:
#上述範例程式碼的呼叫堆疊:
程式從堆疊(stack)上的全域執行期上下文開始執行。
當呼叫 hoisting() 函數時,將一個新的函數執行期上下文推到堆疊(stack)上,並且全域執行期上下文被暫停。
在 hoisting() 執行完成後 , hoisting()執行期上下文從堆疊(stack)中彈出,全域執行期上下文恢復。
這個過程是自解釋的,但並沒有真正解釋我們在執行範例程式碼時所看到的例外。當執行期上下文追蹤程式碼的執行情況時,詞法環境追蹤識別碼到特定變數的對應。詞法環境基本上就是 JavaScript 作用域機制的內部實作。通常,詞法環境與 JavaScript 程式碼的特定結構相關聯,例如一個函數或一個 for 循環程式碼區塊。每當建立函數時,對其創建的詞法環境的引用將在一個名為 [[Environment]] 的內部屬性中傳遞。
所有這些術語涵蓋的是一個簡單而非常合乎邏輯的概念。允許將其分解。詞法環境是一個有趣的名稱,用於追蹤程式碼區塊中的變數和函數。除了追蹤局部變數、函數宣告和參數之外,每個詞法環境還追蹤其父級詞法環境。所以上面的範例程式碼在 JavaScript 引擎中會被這樣解析。上述程式碼的詞法環境,如圖所示:
#註:
如果理解起來有問題,請看以下三篇文章:
深入理解JavaScript中的作用域和上下文
JavaScript 核心概念之作用域與閉包
實例分析JavaScript 作用域
為了在詞法環境中解析標識符, JavaScript 引擎將檢查目前環境的參考。如果沒有找到引用,則透過使用 [[environment]] 移動到外部環境。這將持續進行下去,直到標識符被找到,或拋出一個 ‘not defined'(未定義) 的錯誤。
基本上,JavaScript 程式碼的執行分為兩個階段。第一個階段在目前詞法環境中註冊所有的變數和函數宣告。完成之後,第二個階段的 JavaScript 執行就開始了!
所以要詳細說明第一階段:它在兩個步驟中起作用。
掃描目前函數宣告中的程式碼。函數表達式和箭頭函數會被跳過。對於每個被發現的函數,都會建立一個新的函數,並使用函數名稱將其綁定到環境中。如果標識符的名稱已經存在,那麼它的值就會被覆寫。
然後掃描目前環境的變數。找到使用 var 定義的變數和放置在其他函數之外的變量,並註冊一個標識符,其值初始化為 undefined 。如果存在標識符,則該值將保持不變。
注意:用 let 和 const 定義的是區塊變量,與 var 的處理稍微不同。在另一篇文章中了解更多的內容。
現在你應該已經對詞法環境這個基本概念有了一定的了解,那麼讓我們回到範例程式碼中,並解釋這些問題。
在設定全域上下文時,將對環境進行掃描,並將 hoisting() 函數附加到標識符上。然後在下一步中,變數 notyetdeclared 被註冊,其值初始化為 undefined 。按照這個步驟繼續理解程式碼。
現在我們來解釋範例程式碼中提出的3個問題:
#第6 行,該函數宣告之前為何能訪問?
第1階段, hoisting() 函數已經註冊到了標識符中,當JS程式碼在第2階段的全域執行期上下文中開始執行時,它會查找hoisting 的詞法環境,並在其定義之前找到該函數。
第 1 行,沒有拋出錯誤,是因為這時變數 notyetdeclared 不存在嗎?
同樣的,notyetdeclared 被註冊到了標識符,並在第1階段中初始化為 undefined ,因此不會拋出任何錯誤。
最後,
第 4 行,notyetdeclared 已經在全域作用域內宣告了,為什麼在第 9 行列印時還是 undefined 呢?
現在我們進入函數 hoisting 環境。在第1階段中,notyetdeclared 被註冊並初始化為 undefined,因為在這個詞法環境中,notyetdeclared 的變數尚未註冊。如果第 12 行不包含var 關鍵字,那麼情況就不同了。
希望現在可以清楚地看到,在 JavaScript 中 Hoisting(提升) 只是我們用來解釋背後原理的一個觀點,從技術上來講,函數和變數並不會移動到任何地方。
總結
#以上是JavaScript中Hoisting詳解的詳細內容。更多資訊請關注PHP中文網其他相關文章!