廢話不多說,直奔主題了。javascript的運行原理總結如下:
1、按照html文檔流順序執行javascript代碼
浏覽器是按照文檔流從上到下逐步解析頁面結構和信息的,javascript代碼作為嵌入的腳本作為html文檔的組成部分,所以javascript代碼在加載時的執行順序也是根據腳本標簽<script>的出現順序來確定的。
如果通過腳本標簽<script>的src屬性來引入外部.js文件,那麼它也將按照其語句出現的順序來執行,而且執行過程是文檔加載的一部分。不會因為是外部js文件而延期執行。
2、預編譯和執行順序的關系
首先看如下這段代碼:
<script type="text/javascript"> function hello() { alert("hello"); } hello(); function hello() { alert("hello world"); } hello(); </script>
上面這段js代碼的輸出結果是hello world 、hello world,而不是先輸出hello,再輸出hello world。這是因為javascript並非完全按照順序來解釋執行,而是在解釋之前會對javascript進行一次預編譯,在預編譯的過程中,會把定義式的函數優先執行,也會把所有var變量創建,默認值為undefined,以提高程序的執行效率。也就是說上面的這段代碼其實被JS引擎預編譯成下面這樣:
<script type="text/javascript"> var hello = function() { alert("hello"); }; hello = function() { alert("hello world"); }; hello(); hello(); </script>
通過上面的代碼可以清晰的看到,函數其實也是變量,可以對函數進行賦值。為了防止前面那種情況的出現,可以如下定義成兩個js文件:
<script type="text/javascript"> hello(); function hello() { alert("hello"); } // hello(); </script> <script type="text/javascript"> function hello() { alert("hello world"); } hello(); </script>
上面第一個文件,我把hello()放在了function的前面,也是可以輸出正確結果的。
<script type="text/javascript"> hello(); var hello = function() { alert("hello"); }; // hello(); </script>
如果用上面的這種方法對function函數進行定義,那麼就會報錯,報錯信息如下圖1所示:
這裡報錯hello is not a funtion,這是由於在預編譯的時候,對於用var聲明的變量,雖然最先就處理了,但是變量值是undefined。然後運行hello()的時候,由於前面的hello是undefined,類型沒有確定,所以這裡是hello is not a function。雖然,程序中有定義這個函數,但是定義的位置放在了調用的後面,那麼調用的時候,程序並沒有運行到這裡,所以沒用。
再來看下面的這一段代碼:
<script type="text/javascript"> hello(); function hello() { alert("hello"); } // hello(); </script>
上面的這段代碼雖然調用也是在函數定義的前面,但是這裡是以function關鍵字來定義的,用function來定義的時候,跟var不一樣,function定義的時候就已經把函數的值賦了過去,所以這裡可以運行。
總結:
當javascript引擎解析腳本時,它會在預編譯期對所有聲明的變量和函數進行處理。處理如下:
(1)在執行前會進行類似“預編譯”的操作:首先會創建一個當前執行環境下的活動對象,並將那些用var聲明的變量設置為活動對象的屬性,但是此時這些變量的賦值都是undefined,並將那些以function定義的函數也添加為活動對象的屬性,而且它們的值正是函數的定義。
(2)在解釋執行階段,遇到變量需要解析時,會首先從當前執行環境的活動對象中查找,如果沒有找到而且該執行環境的擁有者有prototype屬性時則會從prototype鏈中查找,否則將會按照作用域鏈查找。遇到var a = ...這樣的語句時會給相應的變量進行賦值(注意:變量的賦值是在解釋執行階段完成的,如果在這之前使用變量,它的值會是undefined)。
(3)綜上,一句話總結就是:變量的聲明在預編譯期,變量的初始化在運行期。
<script type="text/javascript"> alert(a); // 在預編譯期間a變量已經加載,但是用var定義,所以賦值為undefined先,故這裡輸出undefined。 var a = 1; // 這裡給前面的沒有賦值的a進行賦值為1 alert(a); // 這裡輸出的a已經是前面賦值過的,所以輸出1。 </script>
對於上面的這段代碼,輸出結果是:先輸出undefined,後輸出1,分析見代碼備注。
雖然變量和函數聲明可以在文檔任意位置,但是良好的習慣應該是在所有JavaScript代碼之前聲明全局變量和函數,並對變量進行初始化賦值。在函數內部也是先聲明變量,然後再引用。
3、按塊執行javascript代碼
所謂代碼塊就是使用<script>標簽分隔的代碼段。JavaScript解釋器在執行腳本時,是按塊來執行的。通俗地說,就是浏覽器在解析HTML文檔流時,如果遇到一個<script>標簽,則JavaScript解釋器會等到這個代碼塊都加載完後,先對代碼塊進行預編譯,然後再執行。執行完畢後,浏覽器會繼續解析下面的HTML文檔流,同時JavaScript解釋器也准備好處理下一個代碼塊。由於JavaScript是按塊執行的,所以如果在一個JavaScript塊中調用後面塊中聲明的變量或函數就會提示語法錯誤。
<script> alert(a); </script> <script> var a = 1; </script>
上面的這段代碼,由於是兩個代碼塊,先執行完第一個代碼塊,再執行第二個代碼塊。執行第一個代碼塊的時候,變量a沒有聲明,所以報錯,報錯信息是:a is not defined。
<script> var a = 1; </script> <script> alert(a); </script>
雖然說,JavaScript是按塊執行的,但是不同塊都屬於同一個全局作用域,也就是說,塊之間的變量和函數是可以共享的。所以,上面的這兩個代碼塊運行的時候,雖然是兩個代碼塊,但是第一段運行以後,a變量就存在了全局作用域中,此時運行到第二個代碼塊,輸出的a變量就可以調用全局作用域中的a,所以沒有問題。
4、借助事件機制改變javascript執行順序
由於JavaScript是按塊處理代碼,同時又遵循HTML文檔流的解析順序,所以在上面示例中會看到這樣的語法錯誤。但是當文檔流加載完畢,如果再次訪問就不會出現這樣的錯誤。為了安全起見,我們一般在頁面初始化完畢之後才允許JavaScript代碼執行,這樣可以避免網速對JavaScript執行的影響,同時也避開了HTML文檔流對於JavaScript執行的限制。
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>javascript</title> <script> window.onload = function() { alert(a); }; </script> <script> var a = 1; alert("bb"); </script> </head> <body> </body> <script> alert("cc"); </script> </html>
windows.onload = function()表示先在觸發事件上加一個函數,並不立即執行,而是在整個頁面都加載完成以後再開始執行該事件,及function。所以,在windows.onload執行之前,就已經把一些變量加載到了全局區中,所以沒有問題。上面的輸出結果是:先輸出bb,再輸出cc,最後輸出a。
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>javascript</title> <script> window.onload = function() { alert(a); }; // 上面的onload不會執行,只會執行下面的onload window.onload = function() { alert("onload2"); }; </script> <script> var a = 1; alert("bb"); </script> </head> <body> </body> <script> alert("cc"); </script> </html>
如果在一個頁面中存在多個windows.onload事件處理函數,則只有最後一個才是有效的(如上面的代碼所示),為了解決這個問題,可以把所有腳本或調用函數都放在同一個onload事件處理函數中,如下面的代碼所示:
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>javascript</title> <script> window.onload = function() { // 放到一起 alert(a); alert("onload2"); }; </script> <script> var a = 1; alert("bb"); </script> </head> <body> </body> <script> alert("cc"); </script> </html>
5、javascript輸出腳本的執行順序
在JavaScript開發中,經常會使用document對象的write()方法輸出JavaScript腳本。document.write()方法先把輸出的腳本字符串寫入到腳本所在的文檔位置,浏覽器在解析完document.write()所在文檔內容後,繼續解析document.write()輸出的內容,然後才按順序解析後面的HTML文檔。也就是說,JavaScript腳本輸出的代碼字符串會在輸出後馬上被執行。請注意,使用document.write()方法輸出的JavaScript腳本字符串必須放在同時被輸出的<script>標簽中,否則JavaScript解釋器因為不能夠識別這些合法的JavaScript代碼,而作為普通的字符串顯示在頁面文檔中。但是,通過document.write()方法輸出腳本並執行也存在一定的風險,因為不同JavaScript引擎對其執行順序不同,同時不同浏覽器在解析時也會出現Bug。
以上所述是小編給大家介紹的JavaScript語句的執行過程,希望對大家有所幫助。