JavaScript預解析處理過程原來是這回事
一般來說,Javascript代碼的執(zhí)行包括兩個過程:預解析處理過程 和 逐行解讀過程。在代碼逐行解讀前,Javasript引擎需要進行代碼的預處理過程。預解析處理的工作主要是變量提升和給變量分配內(nèi)存,具體過程是在每個作用域中查找var聲明的變量、函數(shù)定義和命名函數(shù)(函數(shù)參數(shù)),找到它們后,在當前作用域中給他們分配內(nèi)存,并給他們設置初始值。預解析設置的初始值分別是:對于var聲明的變量,初始值是undefined,對函數(shù)定義,變量名為函數(shù)名,函數(shù)變量的初始值為函數(shù)定義本身;對命名參數(shù),如果函數(shù)調(diào)用時沒有指定參數(shù)值,則命名參數(shù)的初始值為undefined,如果函數(shù)調(diào)用是指定了參數(shù)值,則命名參數(shù)的初始值為指定的參數(shù)值。
注:對于變量聲明的同時賦值的語句,例如:var a = 9,Javascript引擎對它進行處理時把該語句分拆為兩條語句:var a和a=9,其中,var a在預解析階段進行處理,a=9是賦值表達式,在逐行解讀階段進行賦值。所以預解析中,不管變量聲明時是否有賦值,變量的初始值都是undefiend。
預解析發(fā)生的時機
(1)、遇到<script>標簽時
瀏覽器加載到<script>標簽時,將使用javascript引擎對<script></script>標簽對之間的代碼塊進行預解析:找到函數(shù)定義和函數(shù)體外的所有var聲明的變量,并給它們分配內(nèi)存和設置初始值。
對同名的var變量和函數(shù)變量,只會分配一次棧內(nèi)存。但在堆內(nèi)存中會給函數(shù)變量的初始值分配內(nèi)存。對變量賦初始值時,函數(shù)變量初始值優(yōu)先級高于var變量初始值,而同級別的函數(shù)變量,后定義的函數(shù)優(yōu)先于先定義的函數(shù)。
所以var變量名和函數(shù)變量名相同時,如果內(nèi)存中的變量的值一開始為undefined,但最終內(nèi)存中該變量的初始值會替換為函數(shù)變量的值;否則變量的初始值保持不變。而同名的函數(shù)變量,后面定義的函數(shù)會替換前面定義的函數(shù)。
(2)、遇到函數(shù)時
每一對<script></script>標簽中的代碼預解析完后會立即逐行解讀代碼。在解讀代碼的過程中,如果遇到函數(shù)調(diào)用,此時會在函數(shù)作用域中首先進行預解析處理,預解析處理完才會執(zhí)行函數(shù)代碼。在函數(shù)作用域的預解析規(guī)則是:找到命名函數(shù)、所有var變量和函數(shù)定義,并給它們在函數(shù)作用域中分配內(nèi)存和設置初始值。對同名的var變量、命名參數(shù)和函數(shù)變量,只會分配一次棧內(nèi)存,但在堆內(nèi)存中會給函數(shù)變量的初始值分配內(nèi)存。對變量賦初始值時,函數(shù)變量的值優(yōu)先級最高,其次是命名參數(shù)值。所以命名參數(shù)名和var變量名相同,內(nèi)存中變量的值是參數(shù)值;如果命名參數(shù)名和函數(shù)變量名相同或var變量名和函數(shù)變量名相同,內(nèi)存中變量的值為函數(shù)變量值。
頁面中包含多個<script></script>標簽時的預解析
當頁面中包含多個<script></script>標簽時,javascript引擎會按頁面中<script></script>標簽出現(xiàn)的順序,從上往下對每一個<script></script>標簽對之間的腳本代碼塊分別進行預解析和逐行解讀處理。每一個<script></script>標簽對之間代碼的預解析是全局范圍的,在函數(shù)調(diào)用時發(fā)上發(fā)生的函數(shù)代碼預解析則是針對函數(shù)范圍的。
需要注意的是,變量在預解析處理得到的初始值在逐行解讀代碼過程中會被賦值表達式(帶有=,+=,-=,*=,/=,++,–等運算符號的語句)修改。
示例
我們通過幾個示例來詳細看一下。
預解析變量的優(yōu)先級示例:
<script>
alert("(1)該行結(jié)果是:" + a) // 1
var a = 3; // 2
alert("(2)該行結(jié)果是" + a) // 3
function a (){ // 4
alert(2);
}
var a = 6; // 5
function a(){ // 6
alert(4);
}
alert("(3)該行結(jié)果是"+ a); // 7
</script>
彈出框結(jié)果分別為:
1處彈出的內(nèi)容:
(1)該行結(jié)果是:
function a(){
alert(4);
}
3處彈出的內(nèi)容:
(2)該行結(jié)果是: 3
7處彈出的內(nèi)容:
(3)該行結(jié)果是: 6
上述運行結(jié)果正是預解析和逐行解讀分階段處理的結(jié)果。Javascript引擎遇到<script></script>標簽時,開始按代碼出現(xiàn)的順序進行預解析處理:首先預解析注釋2處的var變量a,給它分配內(nèi)存,并給他賦初始值為"undefined";然后預解析注釋4處的函數(shù)變量a,發(fā)現(xiàn)該變量和已分配內(nèi)存的var變量同名,所以不再對函數(shù)變量a分配棧內(nèi)存,而只給它分配堆內(nèi)存存儲函數(shù)定義,同時會將棧內(nèi)存中的變量a的值修改為函數(shù)變量的初始值function a(){alert(2);};再接著預解析注釋5處的var變量a,該變量與前面預解析得到的函數(shù)變量a同名,所以對該變量也不再分配內(nèi)存,由于函數(shù)變量值優(yōu)先于var變量值,所以此時注釋5處的var變量a初始值undefined不會修改內(nèi)存變量的函數(shù)定義值;最后預解析注釋6處的函數(shù)變量a,發(fā)現(xiàn)它和內(nèi)存中的變量a同名,也不再給它分配內(nèi)存,但會在堆中分配內(nèi)存存儲6處的函數(shù)定義。由于后定義的函數(shù)優(yōu)先級高于前面定義的函數(shù),此時內(nèi)存的變量a的函數(shù)定義值被修改為function a(){alert(4);}。因此最終內(nèi)存中的變量a的初始值為function a(){alert(4)};。至此,預解析完成。
接著進行逐行解讀代碼。在逐行解讀代碼階段,首先解讀到注釋1處代碼,此時會去內(nèi)存中查找變量a,如果找到,讀取變量a的值并輸出到警告對話框中;如果沒找到,將報a is not defined錯誤。上面的預解析的結(jié)果是內(nèi)存中存在變量a,且其值為function a(){alert(4);}。注釋2處的代碼是一個賦值表達式:a=3,執(zhí)行該代碼后,會將內(nèi)存中的變量a的值修改為“3”。所以執(zhí)行到注釋3處代碼時,從內(nèi)存中讀取到的值為“3”。注釋4處定義了一個函數(shù),執(zhí)行時會跳出函數(shù)定義不作任何操作。注釋5處代碼是一個賦值表達式:a=6,執(zhí)行該行代碼后,會將內(nèi)存中的變量a的值修改為“6”。注釋6處又是一個函數(shù)定義,不作解讀。最后執(zhí)行了注釋7處的代碼,從而讀取到值“6”。
歡迎關(guān)注我的公眾號前端歷劫之路
回復關(guān)鍵詞電子書,即可獲取12本前端熱門電子書。
回復關(guān)鍵詞紅寶書第4版,即可獲取最新《JavaScript高級程序設計》(第四版)電子書。
關(guān)注公眾號后,點擊下方菜單即可加我微信,我拉攏了很多IT大佬,創(chuàng)建了一個技術(shù)交流、文章分享群,期待你的加入。
作者:Vam的金豆之路
主要領(lǐng)域:前端開發(fā)
我的微信:maomin9761
微信公眾號:前端歷劫之路