首頁 >web前端 >js教程 >探析瀏覽器執行JavaScript腳本載入與程式碼執行順序_javascript技巧

探析瀏覽器執行JavaScript腳本載入與程式碼執行順序_javascript技巧

WBOY
WBOY原創
2016-05-16 15:20:281691瀏覽

本文主要基於向HTML頁面引入JavaScript的幾種方式,分析HTML中JavaScript腳本的執行順序問題

1. 關於JavaScript腳本執行的阻塞性

JavaScript在瀏覽器中被解析和執行時具有阻塞的特性,也就是說,當JavaScript程式碼執行時,頁面的解析、渲染以及其他資源的下載都要停下來等待腳本執行完畢①。這一點是沒有爭議的,並且在所有瀏覽器中的行為都是一致的,原因也不難理解:瀏覽器需要一個穩定的DOM結構,而JavaScript可能會修改DOM(改變DOM結構或修改某個DOM節點),如果在JavaScript執行的同時還繼續進行頁面的解析,那麼整個解析過程將變得難以控制,解析出錯的可能也變得很大。

然而這裡還有一個問題需要注意,對於外部腳本,還涉及到一個腳本下載的過程,在早期的瀏覽器中,JavaScript文件的下載不僅會阻塞頁面的解析,甚至還會阻塞頁面其他資源的下載(包括其他JavaScript腳本檔案、外部CSS檔案以及圖片等外部資源)。從IE8、firefox3.5、safari4和chrome2開始允許JavaScript並行下載,同時JavaScript檔案的下載也不會阻塞其他資源的下載(舊版本中,JavaScript檔案的下載也會阻塞其他資源的下載)。

註:不同瀏覽器對於同一個網域下的最大連線數有不同的限制,HTTP1.1協定規格中的要求是不能高於2個,但是大多數瀏覽器目前實際提供的最大連線數都多於2個,IE6/7都是2個,IE8提升到了6個,firefox和chrome也是6個,當然這個設定也是可以修改的,詳細內容可以參考:http://www.stevesouders. com/blog/2008/03/20/roundup-on-parallel-connections/

2. 關於腳本的執行順序

瀏覽器是按照從上到下的順序解析頁面,因此正常情況下,JavaScript腳本的執行順序也是從上到下的,即頁面上先出現的程式碼或先被引入的程式碼總是先執行,即使是允許並行下載JavaScript檔案時也是如此。注意我們這裡標紅了"正常情況下",原因是什麼呢?我們知道,在HTML中加入JavaScript程式碼有多種方式,概括如下(不考慮requirejs或seajs等模組載入器):

(1)正常引入:即在頁面中透過<script>標籤引入腳本程式碼或引入外部腳本<br /> </script>

(2)透過document.write方法寫入頁面<script>標籤或程式碼<br /> </script>

(3)透過動態腳本技術,即利用DOM介面建立<script>元素,並設定元素的src,然後再將元素加入DOM。 <br /> </script>

(4)透過Ajax取得腳本內容,然後再建立<script>元素,並設定元素的text,再將元素加入DOM。 <br /> </script>

(5)直接把JavaScript程式碼寫在元素的事件處理程序中或直接作為URL的主體,範例如下:

<!--直接写在元素的事件处理程序中-->
<input type="button" value="点击测试一下" onclick="alert('点击了按钮')"/>
<!--作为URL的主体-->
<a href="javascript:alert('dd')">JS脚本作为URL的主体</a> 

第5種情況對於我們討論的腳本執行順序沒有什麼影響,因此我們這裡只討論前四種情況:

2.1 正常引入腳本時

正常引入腳本時,JavaScript程式碼會按照從上到下的順序執行,不管腳本是不是並行下載,執行時還是按照引入的順序從上到下執行的,我們以下面的DEMO為例:

首先,透過PHP寫了一個腳本,這個腳本接收兩個參數,檔案URL和延遲時間,腳本會在傳入的延遲時間之後,將檔案內容傳送給瀏覽器,腳本如下:

<&#63;php
$url = $_GET['url'];
$delay = $_GET['delay'];
if(isset($delay)){
sleep($delay);
}
echo file_get_contents($url);
&#63;> 

另外我們還定義了兩個JavaScript文件,分別為1.js和2.js,在這個例子中,二者的程式碼分別如下:

1.js

alert("我是第一個腳本");

2.js

alert("我是第二個腳本");

然後,我們在HTML中引入腳本程式碼:

<script src='/delayfile.php&#63;url=http://localhost/js/load/1.js&delay=3' type='text/javascript'></script>
<script type="text/javascript">
alert("我是内部脚本");
</script>
<script src='/delayfile.php&#63;url=http://localhost/js/load/2.js&delay=1' type='text/javascript'></script> 

虽然第一个脚本延迟了3秒才会返回,但是在所有浏览器中,弹出的顺序也都是相同的,即:"我是第一个脚本"->"我是内部脚本"->"我是第二个脚本"

2.2 通过document.write向页面中写入脚本时

document.write在文档流没有关闭的情况下,会将内容写入脚本所在位置结束之后紧邻的位置,浏览器执行完当前短的代码,会接着解析document.write所写入的内容。

注:document.write写入内容的位置还存在一个问题,加入在93f0f5c25f18dab9d176bd4f6de5d30e内部的脚本中写入了93f0f5c25f18dab9d176bd4f6de5d30e标签内部不应该出现的内容,比如dc6dce4a544fdca2df29d5ac0ea9906b等内容标签等,则这段内容的起始位置将是6c04bd5ca3fcae76e30b72ad730ca86d标签的起始位置。

通过document.write写入脚本时存在一些问题,需要分类进行说明:

[1]同一个3f1c4e4b6b16bbbd69b2ee476dc4f83a标签中通过document.write只写入外部脚本:

在这种情况下,外部脚本的执行顺序总是低于引入脚本的标签内的代码,并且按照引入的顺序来执行,我们修改HTML中的代码:

<script src='/delayfile.php&#63;url=http://localhost/js/load/1.js&delay=2' type='text/javascript'></script>
<script type="text/javascript">
document.write('<script type="text/javascript" src="/delayfile.php&#63;url=http://localhost/js/load/2.js"><\/script>');
document.write('<script type="text/javascript" src="/delayfile.php&#63;url=http://localhost/js/load/1.js"><\/script>');
alert("我是内部脚本");
</script> 

这段代码执行完毕之后,DOM将被修改为:

而代码执行的结果也符合DOM中脚本的顺序:"我是第一个脚本"->"我是内部脚本"->"我是第二个脚本"->"我是第一个脚本"

[2]同一个3f1c4e4b6b16bbbd69b2ee476dc4f83a标签中通过document.write只写入内部脚本:

在这种情况下,通过documen.write写入的内部脚本,执行顺序的优先级与写入脚本标签内的代码相同,并且按照写入的先后顺序执行:

我们再修改HTML代码如下:

<script src='/delayfile.php&#63;url=http://localhost/js/load/1.js' type='text/javascript'></script>
<script type="text/javascript">
document.write('<script type="text/javascript">alert("我是docment.write写入的内部脚本")<\/script>');
alert("我是内部脚本");
document.write('<script type="text/javascript">alert("我是docment.write写入的内部脚本2222")<\/script>');
document.write('<script type="text/javascript">alert("我是docment.write写入的内部脚本3333")<\/script>');
</script> 

在这种情况下,document.write写入的脚本被认为与写入位置处的代码优先级相同,因此在所有浏览器中,弹出框的顺序均为:"我是第一个脚本"->"我是document.write写入的内部脚本"->"我是内部脚本"->"我是document.write写入的内部脚本2222"->"我是document.write写入的内部脚本3333"

[3]同一个3f1c4e4b6b16bbbd69b2ee476dc4f83a标签中通过document.write同时写入内部脚本和外部脚本时:

在这种情况下,不同的浏览器中存在一些区别:

在IE9及以下的浏览器中:只要是通过document.write写入的内部脚本,其优先级总是高于document.write写入的外部脚本,并且优先级与写入标签内的代码相同。而通过通过document.write写入的外部脚本,则总是在写入标签的代码执行完毕后,再按照写入的顺序执行;

而在其中浏览器中, 出现在第一个document.write写入的外部脚本之前的内部脚本,执行顺序的优先级与写入标签内的脚本优先级相同,而之后写入的脚本代码,不管是内部脚本还是外部脚本,总是要等到写入标签内的脚本执行完毕后,再按照写入的顺序执行。

我们修改以下HTML中的代码:

<script src='/delayfile.php&#63;url=http://localhost/js/load/1.js' type='text/javascript'></script><script type="text/javascript"> document.write('<script type="text/javascript">alert("我是docment.write写入的内部脚本")<\/script>'); alert("我是内部脚本"); document.write('<script type="text/javascript" src="/delayfile.php&#63;url=http://localhost/js/load/1.js"><\/script>'); document.write('<script type="text/javascript">alert("我是docment.write写入的内部脚本2222")<\/script>'); document.write('<script type="text/javascript" src="/delayfile.php&#63;url=http://localhost/js/load/1.js"><\/script>'); document.write('<script type="text/javascript">alert("我是docment.write写入的内部脚本3333")<\/script>'); alert("我是内部脚本2222");</script> 

在IE9及以下的浏览器中,上面代码执行后弹出的内容为:"我是第一个脚本"->"我是document.write写入的内部脚本"->"我是内部脚本"->"我是document.write写入的内部脚本2222"->"我是document.write写入的内部脚本3333"->"我是内部脚本2222"->"我是第一个脚本"->"我是第一个脚本"

其他浏览器中,代码执行后弹出的内容为:"我是第一个脚本"->"我是document.write写入的内部脚本"->"我是内部脚本"->"我是内部脚本2222"->"我是第一个脚本"->"我是document.write写入的内部脚本2222"->"我是第一个脚本"->"我是document.write写入的内部脚本3333"

如果希望IE及以下的浏览器与其他浏览器保持一致的行为,那么可选的做法就是把引入内部脚本的代码拿出来,单独放在后面一个新的3f1c4e4b6b16bbbd69b2ee476dc4f83a标签内即可,因为后面3f1c4e4b6b16bbbd69b2ee476dc4f83a标签中通过document.write所引入的代码执行顺序肯定是在之前的标签中的代码的后面的。

2.3 通过动态脚本技术添加代码时

通过动态脚本技术添加代码的主要目的在于创建无阻塞脚本,因为通过动态脚本技术添加的代码不会立刻执行,我们可以通过下面的load函数为页面添加动态脚本:

function loadScript(url,callback){
var script = document.createElement("script");
script.type = "text/javascript";
//绑定加载完毕的事件
if(script.readyState){
script.onreadystatechange = function(){
if(script.readyState === "loaded" || script.readyState === "complete"){
callback&&callback();
}
}
}else{
script.onload = function(){
callback&&callback();
}
}
script.src = url;
document.getElementsByTagName("head")[0].appendChild(script);
}

但是通过动态脚本技术添加的外部JavaScript脚本不保证按照添加的顺序执行,这一点可以通过回调或者使用jQuery的html()方法,详细可参考:http://www.jb51.net/article/26446.htm

2.4 通过Ajax注入脚本

通过Ajax注入脚本同样也是添加无阻塞脚本的技术之一,我们首先需要创建一个XMLHttpRequest对象,并且实现get方法,然后通过get方法取得脚本内容并注入到文档中。

代码示例:

我们可以用如下代码封装XMLHttpRequest对象,并封装其get方法:

var xhr = (function(){
function createXhr(){
var xhr ;
if(window.XMLHttpRequest){
xhr = new XMLHttpRequest();
}else if(window.ActiveXObject){
var xhrVersions = ['MSXML2.XMLHttp','MSXML2.XMLHttp.3.0','MSXML2.XMLHttp.6.0'], i, len;
for(i = 0, len = xhrVersions.length; i < len ; i++){
try{
xhr = new ActiveXObject(xhrVersions[i]);
}catch(e){
}
}
}else{
throw new Error("无法创建xhr对象");
}
return xhr;
}
function get(url,async,callback){
var xhr = createXhr();
xhr.onreadystatechange = function(){
if(xhr.readyState == 4){
if((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304){
callback&&callback(xhr.responseText);
}else{
alert("请求失败,错误码为" + xhr.status);
}
}
}
xhr.open("get",url,async);
xhr.send(null);
}
return {
get:get
}
}()) 

然后基于xhr对象,再创建loadXhrScript函数:

function loadXhrScript(url,async, callback){ if(async == undefined){ async = true; } xhr.get(url,async,function(text){ var script = document.createElement("script"); script.type = "text/javascript"; script.text = text; document.body.appendChild(script); });} 

我们上面的get方法添加了一个参数,即是否异步,那么如果我们采用同步方法,通过Ajax注入的脚本肯定是按照添加的顺序执行;反之,如果我们采用异步的方案,那么添加的脚本的执行顺序肯定是无法确定的。

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn