javascript在html中的加載順序


參考:[1]http://coolshell.cn/articles/9749.html(酷殼)

         [2]http://shaozhuqing.com/?p=2756

 

顏色標注分別來自於鏈接地址內容


 

    通常來說,瀏覽器對於Javascript的運行有兩大特性:1)載入后馬上執行,2)執行時會阻塞頁面后續的內容(包括頁面的渲染、其它資源的下載)。於是,如果有多個js文件被引入,那么對於瀏覽器來說,這些js文件被被串行地載入,並依次執行。

  因為javascript可能會來操作HTML文檔的DOM樹,所以,瀏覽器一般都不會像並行下載css文件並行下載js文件,因為這是js文件的特殊性造成的。所以,如果你的javascript想操作后面的DOM元素,基本上來說,瀏覽器都會報錯說對象找不到。因為Javascript執行時,后面的HTML被阻塞住了,DOM樹時還沒有后面的DOM結點。所以程序也就報錯了。

 

傳統的方式

 

<script type="text/javascript"        src="http://coolshell.cn/asyncjs/alert.js"></script>

 

  基本上來說,head里的 <script>標簽會阻塞后續資源的載入以及整個頁面的生成。(在酷殼的主頁中有這個例子) 。所以,你知道為什么有很多網站把javascript放在網頁的最后面了,要么就是動用了window.onload或是docmuemt ready之類的事件。

 

這是因為 放在<head>標簽中的javascript會在頁面一旦打開的時候進行加載,因此,如果這里面的js對DOM樹進行加載的話,很容易就會因為找不到!

 

怎樣實現等待DOM被完全加載后才調用JS文件

(1) 將所有的JS文件都放在</HTML>后面后再進行調用

(2) 將JS代碼放在標簽里面

看下面的代碼:

 

<html>
<head>
<script type="text/javascript" src="js/jquery-1.9.0.min.js"></script>

</head>
<script>
function test(){
	$('#mybtn').click(function(){
	alert("123")
	});
}
window.onload=test();
</script>
<body>
<div id="toolbar">
	<input type='button' value="click me" id="mybtn"></input>
</div>
<div id="myDiv" style="height:200px;width:200px;background-color:#232345">
</div>
</body>
</html>

 

 $('#mybtn')會因為找不到mybtn的標簽而報錯

 

修改代碼如下:

<html>
<head>
<script type="text/javascript" src="js/jquery-1.9.0.min.js"></script>

</head>

<body>
<div id="toolbar">
	<input type='button' value="click me" id="mybtn"></input>
</div>
<div id="myDiv" style="height:200px;width:200px;background-color:#232345">
</div>
</body>
</html>
<script>
function test(){
	$('#mybtn').click(function(){
	alert("123")
	});
}
window.onload=test();
</script>

 可以順利實現彈出alert,另外,通過事件的方法也可以改變JS的調用順序

<html>
<head>
<script type="text/javascript" src="js/jquery-1.9.0.min.js"></script>

</head>
<script>
function test(){
	$('#mybtn').click(function(){
	alert("123")
	});
}
</script>
<body onload="test()">
<div id="toolbar">
	<input type='button' value="click me" id="mybtn"></input>
</div>
<div id="myDiv" style="height:200px;width:200px;background-color:#232345">
</div>
</body>
</html>

 

 

因為絕大多數的Javascript代碼並不需要等頁面,所以,我們異步載入的功能。那么我們怎么異步載入呢?

http://coolshell.cn/articles/9749.html(酷殼)

 


 

預編譯

請先看下面的一段代碼!

 1 <scripttype="text/javascript">
 2 
 3         functionHello() {
 4 
 5             alert("Hello");
 6 
 7         }
 8 
 9         Hello();
10 
11         functionHello() {
12 
13             alert("Hello World");
14 
15         }
16 
17         Hello();
18 
19     </script>

輸出結果並不是我們想象的那樣,而是兩次的輸出結果都是hello,world!

這是因為Javascript並非完全的按順序解釋執行,而是在解釋之前會對Javascript進行一次“預編譯”,在預編譯的過程中,會把定義式的函數優先執行,也會把所有var變量創建,默認值為undefined,以提高程序的執行效率。也就是說上面的一段代碼其實被JS引擎預編譯為這樣的形式:

 

 1  <scripttype="text/javascript">
 2 
 3         varHello = function() {
 4 
 5             alert("Hello");
 6 
 7         }
 8 
 9         Hello = function() {
10 
11             alert("Hello World");
12 
13         }
14 
15         Hello();
16 
17         Hello();
18 
19     </script>

后面的hello會把前面的hello給覆蓋掉,因此會出現兩次執行的結果都是hello,world.

當JavaScript引擎解析腳本時,它會在預編譯期對所有聲明的變量和函數進行處理。

做如下處理:

1. 在執行前會進行類似“預編譯”的操作:首先會創建一個當前執行環境下的活動對象,並將那些用var申明的變量設置為活動對象的屬性,但是此時這些變量的賦值都是undefined,並將那些以function定義的函數也添加為活動對象的屬性,而且它們的值正是函數的定義。

2. 在解釋執行階段,遇到變量需要解析時,會首先從當前執行環境的活動對象中查找,如果沒有找到而且該執行環境的擁有者有prototype屬性時則會從prototype鏈中查找,否則將會按照作用域鏈查找。遇到var a = ...這樣的語句時會給相應的變量進行賦值(注意:變量的賦值是在解釋執行階段完成的,如果在這之前使用變量,它的值會是undefined) 所以,就會出現當JavaScript解釋器執行下面腳本時不會報錯:

alert(a);                            // 返回值undefined

var a =1;

alert(a);                            // 返回值1

由於變量聲明是在預編譯期被處理的,所以在執行期間對於所有代碼來說,都是可見的。但是,你也會看到,執行上面代碼,提示的值是undefined,而不是1。這是因為,變量初始化過程發生在執行期,而不是預編譯期。在執行期,JavaScript解釋器是按着代碼先后順序進行解析的,如果在前面代碼行中沒有為變量賦值,則JavaScript解釋器會使用默認值undefined。由於在第二行中為變量a賦值了,所以在第三行代碼中會提示變量a的值為1,而不是undefined。

同理,下面示例在函數聲明前調用函數也是合法的,並能夠被正確解析,所以返回值為1。

 1 f();                                 // 調用函數,返回值1
 2 
 3 function f(){
 4 
 5     alert(1);
 6 
 7 }
 8 
 9 但是,如果按下面方式定義函數,則JavaScript解釋器會提示語法錯誤。
10 
11 f();                                 // 調用函數,返回語法錯誤
12 
13 var f = function(){
14 
15     alert(1);
16 
17 }

 

這是因為,上面示例中定義的函數僅作為值賦值給變量f,所以在預編譯期,JavaScript解釋器只能夠為聲明變量f進行處理,而對於變量f的值,只能等到執行期時按順序進行賦值,自然就會出現語法錯誤,提示找不到對象f。

 

總結: JS加載包含預編譯和執行兩個階段。 編譯階段會對所有的var變量和function進行掃描,並將var變量初始化為undefined類型,而function則被初始化為函數值。

   到了執行階段,JS從上面往下面依順序執行,遇到var變量便進行賦值(因此,在賦值之前進行調用的話會出現錯誤).遇到函數變量的話會從活動對象中尋找函數

 

 

再見一些例子:

 1 <script type="text/javascript">
 2 
 3 /*在預編譯過程中func是window環境下的活動對象中的一個屬性,值是一個函數,覆蓋了undefined值*/
 4 
 5 alert(func); //function func(){alert("hello!")}
 6 
 7 var func = "this is a variable"
 8 
 9 function func(){
10 
11 alert("hello!")
12 
13 }
14 
15 /*在執行過程中遇到了var重新賦值為"this is a variable"*/
16 
17 alert(func);  //this is a variable
18 
19 </script>

(此例子也是來自於參考2)

雖然變量和函數聲明可以在文檔任意位置,但是良好的習慣應該是在所有JavaScript代碼之前聲明全局變量和函數,並對變量進行初始化賦值。在函數內部也是先聲明變量,然后再引用。

 


 

按塊執行JavaScript代碼

所謂代碼塊就是使用<script>標簽分隔的代碼段。例如,下面兩個<script>標簽分別代表兩個JavaScript代碼塊。

 

<script>

// JavaScript代碼塊1

var a =1;

</script>

<script>

// JavaScript代碼塊2

function f(){

    alert(1);

}

</script>

 

 

 

JavaScript解釋器在執行腳本時,是按塊來執行的。通俗地說,就是瀏覽器在解析HTML文檔流時,如果遇到一個<script>標簽,則JavaScript解釋器會等到這個代碼塊都加載完后,先對代碼塊進行預編譯,然后再執行。執行完畢后,瀏覽器會繼續解析下面的HTML文檔流,同時JavaScript解釋器也准備好處理下一個代碼塊。

由於JavaScript是按塊執行的,所以如果在一個JavaScript塊中調用后面塊中聲明的變量或函數就會提示語法錯誤。例如,當JavaScript解釋器執行下面代碼時就會提示語法錯誤,顯示變量a未定義,對象f找不到。

 

<script>

// JavaScript代碼塊1

alert(a);

f();

</script>

<script>

// JavaScript代碼塊2

var a =1;

function f(){

    alert(1);

}

</script>

 

 

 

雖然說,JavaScript是按塊執行的,但是不同塊都屬於同一個全局作用域,也就是說,塊之間的變量和函數是可以共享的。

 

 


 

借助事件機制改變JavaScript執行順序

由於JavaScript是按塊處理代碼,同時又遵循HTML文檔流的解析順序,所以在上面示例中會看到這樣的語法錯誤。但是當文檔流加載完畢,如果再次訪問就不會出現這樣的錯誤。例如,把訪問第2塊代碼中的變量和函數的代碼放在頁面初始化事件函數中,就不會出現語法錯誤了。

<script>

// JavaScript代碼塊1
window.onload = function(){        // 頁面初始化事件處理函數
    alert(a);
    f();
}
</script>
<script>
// JavaScript代碼塊2
var a =1;
function f(){
    alert(1);
}
</script>

 

為了安全起見,我們一般在頁面初始化完畢之后才允許JavaScript代碼執行,這樣可以避免網速對JavaScript執行的影響,同時也避開了HTML文檔流對於JavaScript執行的限制。

注意如果在一個頁面中存在多個windows.onload事件處理函數,則只有最后一個才是有效的,為了解決這個問題,可以把所有腳本或調用函數都放在同一個onload事件處理函數中,例如:

window.onload = function(){

    f1();

    f2();

    f3();

}

(關於怎樣在文件中執行多個window.onload函數可以參見博客中的window.onload函數)

而且通過這種方式可以改變函數的執行順序,方法是:簡單地調整onload事件處理函數中調用函數的排列順序。

除了頁面初始化事件外,我們還可以通過各種交互事件來改變JavaScript代碼的執行順序,如鼠標事件、鍵盤事件及時鍾觸發器等方法。

 

 

 


 

JavaScript輸出腳本的執行順序

在JavaScript開發中,經常會使用document對象的write()方法輸出JavaScript腳本。那么這些動態輸出的腳本是如何執行的呢?例如:

document.write('<script type="text/javascript">');

document.write('f();');

document.write('function f(){');

document.write('alert(1);');

document.write('}');

document.write('</script>');

運行上面代碼,我們會發現:document.write()方法先把輸出的腳本字符串寫入到腳本所在的文檔位置,瀏覽器在解析完document.write()所在文檔內容后,繼續解析document.write()輸出的內容,然后才按順序解析后面的HTML文檔。

也就是說,JavaScript腳本輸出的代碼字符串會在輸出后馬上被執行。

請注意,使用document.write()方法輸出的JavaScript腳本字符串必須放在同時被輸出的<script>標簽中,否則JavaScript解釋器因為不能夠識別這些合法的JavaScript代碼,而作為普通的字符串顯示在頁面文檔中。例如,下面的代碼就會把JavaScript代碼顯示出來,而不是執行它。

document.write('f();');

document.write('function f(){');

document.write('alert(1);');

document.write(');');

但是,通過document.write()方法輸出腳本並執行也存在一定的風險,因為不同JavaScript引擎對其執行順序不同,同時不同瀏覽器在解析時也會出現Bug。

Ø 問題一,找不到通過document.write()方法導入的外部JavaScript文件中聲明的變量或函數。例如,看下面示例代碼。

document.write('<script type="text/javascript" src="test.js">

</script>');

document.write('<script type="text/javascript">');

document.write('alert(n);');  // IE提示找不到變量n

document.write('</script>');

alert(n+1);                          // 所有瀏覽器都會提示找不到變量n

外部JavaScript文件(test.js)的代碼如下:

var n = 1;

分別在不同瀏覽器中進行測試,會發現提示語法錯誤,找不到變量n。也就是說,如果在JavaScript代碼塊中訪問本代碼塊中使用document.write()方法輸出的腳本中導入的外部JavaScript文件所包含的變量,會顯示語法錯誤。同時,如果在IE瀏覽器中,不僅在腳本中,而且在輸出的腳本中也會提示找不到輸出的導入外部JavaScript文件的變量(表述有點長和繞,不懂的讀者可以嘗試運行上面代碼即可明白)。

Ø 問題二,不同JavaScript引擎對輸出的外部導入腳本的執行順序略有不同。例如,看下面示例代碼。

<script type="text/javascript">

document.write('<script type="text/javascript" src="test1.js">

</script>');

document.write('<script type="text/javascript">');

document.write('alert(2);')

document.write('alert(n+2);');

document.write('</script>');

</script>

<script type="text/javascript">

alert(n+3);

</script>

外部JavaScript文件(test1.js)的代碼如下所示。

var n = 1;

alert(n);

在IE瀏覽器中的執行順序如圖1-6所示。

 

圖1-6  IE 7瀏覽器的執行順序和提示的語法錯誤

在符合DOM標准的瀏覽器中的執行順序與IE瀏覽器不同,且沒有語法錯誤,如圖1-7所示的是在Firefox 3.0瀏覽器中的執行順序。

 

圖1-7  Firefox 3瀏覽器的執行順序和提示的語法錯誤

解決不同瀏覽器存在的不同執行順序,以及可能存在Bug。我們可以把凡是使用輸出腳本導入的外部文件,都放在獨立的代碼塊中,這樣根據上面介紹的JavaScript代碼塊執行順序,就可以避免這個問題。例如,針對上面示例,可以這樣設計:

<script type="text/javascript">

document.write('<script type="text/javascript" src="test1.js"></script>');

</script>

<script type="text/javascript">

document.write('<script type="text/javascript">');

document.write('alert(2);') ; // 提示2

document.write('alert(n+2);'); // 提示3

document.write('</script>');

alert(n+3); // 提示4

</script>

<script type="text/javascript">

alert(n+4); // 提示5

</script>

這樣在不同瀏覽器中都能夠按順序執行上面代碼,且輸出順序都是1、2、3、4和5。存在問題的原因是:輸出導入的腳本與當前JavaScript代碼塊之間的矛盾。如果單獨輸出就不會發生沖突了。

 

 

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM