瀏覽器環境下JavaScript腳本加載與執行探析之代碼執行順序

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

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

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

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

注:不一樣瀏覽器對於同一個域名下的最大鏈接數有不一樣的限制,HTTP1.1協議規範中的要求是不能高於2個,可是大多數瀏覽器目前實際提供的最大鏈接數都多於2個,IE6/7都是2個,IE8提高到了6個,firefox和chrome也是6個,固然這個設置也是能夠修改的,詳細內容能夠參考:http://www.stevesouders.com/blog/2008/03/20/roundup-on-parallel-connections/java

2. 關於腳本的執行順序

瀏覽器是按照從上到下的順序解析頁面,所以正常狀況下,JavaScript腳本的執行順序也是從上到下的,即頁面上先出現的代碼或先被引入的代碼老是被先執行,即便是容許並行下載JavaScript文件時也是如此。注意咱們這裏標紅了"正常狀況下",緣由是什麼呢?咱們知道,在HTML中加入JavaScript代碼有多種方式,歸納以下(不考慮requirejs或seajs等模塊加載器):chrome

(1)正常引入:即在頁面中經過<script>標籤引入腳本代碼或者引入外部腳本瀏覽器

(2)經過document.write方法向頁面寫入<script>標籤或代碼app

(3)經過動態腳本技術,即利用DOM接口建立<script>元素,並設置元素的src,而後再將元素添加進DOM中。異步

(4)經過Ajax獲取腳本內容,而後再建立<script>元素,並設置元素的text,再將元素添加進DOM中。async

(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和延遲時間,腳本會在傳入的延遲時間以後,將文件內容發送給瀏覽器,腳本以下:

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

另外咱們還定義了兩個JavaScript文件,分別爲1.js和2.js,在這個例子中,兩者的代碼分別以下:

1.js

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

2.js

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

而後,咱們在HTML中引入腳本代碼:

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

雖然第一個腳本延遲了3秒纔會返回,可是在全部瀏覽器中,彈出的順序也都是相同的,即:"我是第一個腳本"->"我是內部腳本"->"我是第二個腳本"

2.2 經過document.write向頁面中寫入腳本時

document.write在文檔流沒有關閉的狀況下,會將內容寫入腳本所在位置結束以後緊鄰的位置,瀏覽器執行完當前短的代碼,會接着解析document.write所寫入的內容。

注:document.write寫入內容的位置還存在一個問題,加入在<head>內部的腳本中寫入了<head>標籤內部不該該出現的內容,好比<div>等內容標籤等,則這段內容的起始位置將是<body>標籤的起始位置。

經過document.write寫入腳本時存在一些問題,須要分類進行說明:

[1]同一個<script>標籤中經過document.write只寫入外部腳本

在這種狀況下,外部腳本的執行順序老是低於引入腳本的標籤內的代碼,而且按照引入的順序來執行,咱們修改HTML中的代碼:

<script src='/delayfile.php?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?url=http://localhost/js/load/2.js"><\/script>');
        document.write('<script type="text/javascript" src="/delayfile.php?url=http://localhost/js/load/1.js"><\/script>');
        alert("我是內部腳本");
</script>

這段代碼執行完畢以後,DOM將被修改成:

而代碼執行的結果也符合DOM中腳本的順序:"我是第一個腳本"->"我是內部腳本"->"我是第二個腳本"->"我是第一個腳本"

[2]同一個<script>標籤中經過document.write只寫入內部腳本

在這種狀況下,經過documen.write寫入的內部腳本,執行順序的優先級與寫入腳本標籤內的代碼相同,而且按照寫入的前後順序執行:

咱們再修改HTML代碼以下:

<script src='/delayfile.php?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]同一個<script>標籤中經過document.write同時寫入內部腳本和外部腳本時:

在這種狀況下,不一樣的瀏覽器中存在一些區別:

在IE9及如下的瀏覽器中:只要是經過document.write寫入的內部腳本,其優先級老是高於document.write寫入的外部腳本,而且優先級與寫入標籤內的代碼相同。而經過經過document.write寫入的外部腳本,則老是在寫入標籤的代碼執行完畢後,再按照寫入的順序執行;

而在其中瀏覽器中, 如今第一個document.write寫入的外部腳本以前內部腳本,執行順序的優先級與寫入標籤內的腳本優先級相同,而以後寫入的腳本代碼,不論是內部腳本仍是外部腳本,老是要等到寫入標籤內的腳本執行完畢後,再按照寫入的順序執行

咱們修改如下HTML中的代碼:

<script src='/delayfile.php?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?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?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及如下的瀏覽器與其餘瀏覽器保持一致的行爲,那麼可選的作法就是把引入內部腳本的代碼拿出來,單獨放在後面一個新的<script>標籤內便可,由於後面<script>標籤中經過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.cnblogs.com/sanshi/archive/2011/02/28/1967367.html

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]);
                    if(xhr){
                        break;
                    }
                }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注入的腳本確定是按照添加的順序執行;反之,若是咱們採用異步的方案,那麼添加的腳本的執行順序確定是沒法肯定的。

相關文章
相關標籤/搜索