您好,登錄后才能下訂單哦!
這篇文章將為大家詳細講解有關javascript變量提升的相關知識有哪些,小編覺得挺實用的,因此分享給大家做個參考,希望大家閱讀完這篇文章后可以有所收獲。
咱們先看段代碼,你覺得下面這段代碼輸出的結果是什么?
showName() console.log(myname) var myname = '極客時間' function showName() { console.log('函數showName被執行'); }
使用過 JavaScript 開發的程序員應該都知道,JavaScript 是按順序執行的。若按照這個邏輯來理解的話,那么:
當執行到第 1 行的時候,由于函數 showName 還沒有定義,所以執行應該會報錯;
同樣執行第 2 行的時候,由于變量 myname 也未定義,所以同樣也會報錯。
然而實際執行結果卻并非如此, 如下圖:
第 1 行輸出“函數 showName 被執行”,第 2 行輸出“undefined”,這和前面想象中的順序執行有點不一樣啊!
通過上面的執行結果,你應該已經知道了函數或者變量可以在定義之前使用,那如果使用沒有定義的變量或者函數,JavaScript 代碼還能繼續執行嗎?為了驗證這點,我們可以刪除第 3 行變量 myname 的定義,如下所示:
showName() console.log(myname) function showName() { console.log('函數showName被執行'); }
然后再次執行這段代碼時,JavaScript 引擎就會報錯,結果如下:
從上面兩段代碼的執行結果來看,我們可以得出如下三個結論:
在執行過程中,若使用了未聲明的變量,那么 JavaScript 執行會報錯。
在一個變量定義之前使用它,不會出錯,但是該變量的值會為 undefined,而不是定義時的值。
在一個函數定義之前使用它,不會出錯,且函數能正確執行。
第一個結論很好理解,因為變量沒有定義,這樣在執行 JavaScript 代碼時,就找不到該變量,所以 JavaScript 會拋出錯誤。
但是對于第二個和第三個結論,就挺讓人費解的:
變量和函數為什么能在其定義之前使用?這似乎表明 JavaScript 代碼并不是一行一行執行的。
同樣的方式,變量和函數的處理結果為什么不一樣?比如上面的執行結果,提前使用的 showName 函數能打印出來完整結果,但是提前使用的 myname 變量值卻是 undefined,而不是定義時使用的“極客時間”這個值。
要解釋這兩個問題,你就需要先了解下什么是變量提升。
不過在介紹變量提升之前,我們先通過下面這段代碼,來看看什么是 JavaScript 中的聲明和賦值。
var myname = '極客時間'
這段代碼你可以把它看成是兩行代碼組成的:
var myname //聲明部分 myname = '極客時間' //賦值部分
如下圖所示:
上面是變量的聲明和賦值,那接下來我們再來看看函數的聲明和賦值,結合下面這段代碼:
function foo(){ console.log('foo') } var bar = function(){ console.log('bar') }
第一個函數 foo 是一個完整的函數聲明,也就是說沒有涉及到賦值操作;第二個函數是先聲明變量 bar,再把function(){console.log(‘bar’)}賦值給 bar。為了直觀理解,你可以參考下圖:
好了,理解了聲明和賦值操作,那接下來我們就可以聊聊什么是變量提升了。
所謂的變量提升,是指在 JavaScript 代碼執行過程中,JavaScript 引擎把變量的聲明部分和函數的聲明部分提升到代碼開頭的“行為”。變量被提升后,會給變量設置默認值,這個默認值就是我們熟悉的 undefined。
下面我們來模擬下實現:
/* * 變量提升部分 */// 把變量 myname提升到開頭,// 同時給myname賦值為undefinedvar myname = undefined// 把函數showName提升到開頭function showName() { console.log('showName被調用');}/* * 可執行代碼部分 */showName()console.log(myname)// 去掉var聲明部分,保留賦值語句myname = '極客時間'
為了模擬變量提升的效果,我們對代碼做了以下調整,如下圖:
從圖中可以看出,對原來的代碼主要做了兩處調整:
第一處是把聲明的部分都提升到了代碼開頭,如變量 myname 和函數 showName,并給變量設置默認值 undefined;
第二處是移除原本聲明的變量和函數,如var myname = '極客時間’的語句,移除了 var 聲明,整個移除 showName 的函數聲明。
通過這兩步,就可以實現變量提升的效果。你也可以執行這段模擬變量提升的代碼,其輸出結果和第一段代碼應該是完全一樣的。
通過這段模擬的變量提升代碼,相信你已經明白了可以在定義之前使用變量或者函數的原因——函數和變量在執行之前都提升到了代碼開頭。
從概念的字面意義上來看,“變量提升”意味著變量和函數的聲明會在物理層面移動到代碼的最前面,正如我們所模擬的那樣。但,這并不準確。實際上變量和函數聲明在代碼里的位置是不會改變的,而且是在編譯階段被 JavaScript 引擎放入內存中。對,你沒聽錯,一段 JavaScript 代碼在執行之前需要被 JavaScript 引擎編譯,編譯完成之后,才會進入執行階段。大致流程你可以參考下圖:
那么編譯階段和變量提升存在什么關系呢?
為了搞清楚這個問題,我們還是回過頭來看上面那段模擬變量提升的代碼,為了方便介紹,可以把這段代碼分成兩部分。
第一部分:變量提升部分的代碼。
var myname = undefined function showName() { console.log('函數showName被執行'); }
第二部分:執行部分的代碼。
showName() console.log(myname) myname = '極客時間'
下面我們就可以把 JavaScript 的執行流程細化,如下圖所示:
從上圖可以看出,輸入一段代碼,經過編譯后,會生成兩部分內容:執行上下文(Execution context)和可執行代碼。
執行上下文是 JavaScript 執行一段代碼時的運行環境,比如調用一個函數,就會進入這個函數的執行上下文,確定該函數在執行期間用到的諸如 this、變量、對象以及函數等。
關于執行上下文的細節,我會在下一篇文章《08 | 調用棧:為什么 JavaScript 代碼會出現棧溢出?》做詳細介紹,現在你只需要知道,在執行上下文中存在一個變量環境的對象(Viriable Environment),該對象中保存了變量提升的內容,比如上面代碼中的變量 myname 和函數 showName,都保存在該對象中。
你可以簡單地把變量環境對象看成是如下結構:
VariableEnvironment: myname -> undefined, showName ->function : {console.log(myname)
了解完變量環境對象的結構后,接下來,我們再結合下面這段代碼來分析下是如何生成變量環境對象的。
showName() console.log(myname) var myname = '極客時間' function showName() { console.log('函數showName被執行'); }
我們可以一行一行來分析上述代碼:
第 1 行和第 2 行,由于這兩行代碼不是聲明操作,所以 JavaScript 引擎不會做任何處理;
第 3 行,由于這行是經過 var 聲明的,因此 JavaScript 引擎將在環境對象中創建一個名為 myname 的屬性,并使用 undefined 對其初始化;
第 4 行,JavaScript 引擎發現了一個通過 function 定義的函數,所以它將函數定義存儲到堆 (HEAP)中,并在環境對象中創建一個 showName 的屬性,然后將該屬性值指向堆中函數的位置(不了解堆也沒關系,JavaScript 的執行堆和執行棧我會在后續文章中介紹)。
這樣就生成了變量環境對象。接下來 JavaScript 引擎會把聲明以外的代碼編譯為字節碼,至于字節碼的細節,我也會在后面文章中做詳細介紹,你可以類比如下的模擬代碼:
showName() console.log(myname) myname = '極客時間'
好了,現在有了執行上下文和可執行代碼了,那么接下來就到了執行階段了。
JavaScript 引擎開始執行“可執行代碼”,按照順序一行一行地執行。下面我們就來一行一行分析下這個執行過程:
當執行到 showName 函數時,JavaScript 引擎便開始在變量環境對象中查找該函數,由于變量環境對象中存在該函數的引用,所以 JavaScript 引擎便開始執行該函數,并輸出“函數 showName 被執行”結果。
接下來打印“myname”信息,JavaScript 引擎繼續在變量環境對象中查找該對象,由于變量環境存在 myname 變量,并且其值為 undefined,所以這時候就輸出 undefined。
接下來執行第 3 行,把“極客時間”賦給 myname 變量,賦值后變量環境中的 myname 屬性值改變為“極客時間”,變量環境如下所示:
VariableEnvironment: myname -> "極客時間", showName ->function : {console.log(myname)
好了,以上就是一段代碼的編譯和執行流程 。
現在你已經知道了,在執行一段 JavaScript 代碼之前,會編譯代碼,并將代碼中的函數和變量保存到執行上下文的變量環境中,那么如果代碼中出現了重名的函數或者變量,JavaScript 引擎會如何處理?
我們先看下面這樣一段代碼:
function showName() { console.log('極客邦'); } showName(); function showName() { console.log('極客時間'); } showName();
在上面代碼中,我們先定義了一個 showName 的函數,該函數打印出來“極客邦”;然后調用 showName,并定義了一個 showName 函數,這個 showName 函數打印出來的是“極客時間”;最后接著繼續調用 showName。那么你能分析出來這兩次調用打印出來的值是什么嗎?
我們來分析下其完整執行流程:
首先是編譯階段。遇到了第一個 showName 函數,會將該函數體存放到變量環境中。接下來是第二個 showName 函數,繼續存放至變量環境中,但是變量環境中已經存在一個 showName 函數了,此時,第二個 showName 函數會將第一個 showName 函數覆蓋掉。這樣變量環境中就只存在第二個 showName 函數了。
接下來是執行階段。先執行第一個 showName 函數,但由于是從變量環境中查找 showName 函數,而變量環境中只保存了第二個 showName 函數,所以最終調用的是第二個函數,打印的內容是“極客時間”。第二次執行 showName 函數也是走同樣的流程,所以輸出的結果也是“極客時間”。
綜上所述,一段代碼如果定義了兩個相同名字的函數,那么最終生效的是最后一個函數。
關于“javascript變量提升的相關知識有哪些”這篇文章就分享到這里了,希望以上內容可以對大家有一定的幫助,使各位可以學到更多知識,如果覺得文章不錯,請把它分享出去讓更多的人看到。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。