Hoisting的定義
首先,看看mdn對它的解讀:
變量提升(Hoisting)被認為是, JAVAscript中執行上下文 (特別是創建和執行階段)工作方式的一種認識。在 ECMAScript® 2015 Language Specification 之前的JavaScript文檔中找不到變量提升(Hoisting)這個詞。不過,需要注意的是,開始時,這個概念可能比較難理解,甚至惱人。
我們可以理解成,在編譯的階段,js引擎幫我們把變量和函數的聲明放在最前面,但實際上變量和函數聲明在代碼里的位置是不會動的。
知道了個大概后,我們從流程上來說說吧。
創建階段
當JS引擎得到我們的腳本時,它做的第一件事就是為我們代碼中的數據設置內存。在這一點上沒有執行任何代碼,它只是在為執行準備一切。函數聲明和變量的存儲方式是不同的。函數是以對整個函數的引用來存儲的。
流程1
我們可以看到圖中的內容,但是如果這個時候,遇到了變量會怎么樣呢,尤其是ES6中的const和let,讓我們接著往下看:
流程2
從圖中我們可以總結出來,用let,const聲明的變量以uninitialized來存儲的。
這里就解釋了,為什么我們用let和const聲明的,不能在它之前使用,也就是暫時性死區。
那用var關鍵字來聲明的話,結果你猜到了嘛,我們來看圖:
流程3
很顯然,我們發現,用var關鍵字聲明的變量是以默認值undefined來存儲的。
這里是平時面試會被問到的一個考點,現在看來不過如此。
小結一下,他們的區別:
- 用var關鍵字聲明的變量是以默認值undefined來存儲的。
- 用let,const聲明的變量以uninitialized來存儲的。
現在,創建階段已經完成,我們可以實際執行代碼了。
讓我們看看,如果我們在聲明函數或任何變量之前,在文件頂部有3個console.log語句,會發生什么。
執行階段
由于函數是以對整個函數代碼的引用來存儲的,我們甚至可以在創建函數的那一行之前調用它們,我們來看看結果是怎么樣的:
流程4
我們發現,輸出的內容是5,也就是能夠正常的運行,那么遇到var聲明的變量呢,我們來看圖:
流程5
我們從結果中可以看到,當我們引用一個在其聲明前用var關鍵字聲明的變量時,它將簡單地返回其存儲的默認值:undefined。
這就是我們說的怪異行為,所以我們盡可能的不去使用它,讓代碼更加的規范。
這個時候,ES6中引出了const和let。
它的提出,就是為了防止意外地引用未定義的變量,就像我們用var關鍵字一樣,每當我們試圖訪問未初始化的變量時,我們希望它會拋出一個ReferenceError。
流程6
這也就是我們通常意義上所說的暫時性死區,我們引用const定義的變量,在聲明前調用,就會出現Error。
接下來的步驟,就如我們看到的那樣子,當引擎通過我們實際聲明變量的那一行時,內存中的值會被我們實際聲明的值所覆蓋,如下圖所示:
流程7
小結
- 在我們執行代碼之前,函數和變量被存儲在內存中,以獲得一個執行環境。這就是所謂的Hoisting。
- 函數是以對整個函數的引用來存儲的,帶有var關鍵字的變量的值是未定義的,帶有let和const關鍵字的變量是未初始化的。