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

来源:http://www.cnblogs.com/tracylin/archive/2016/01/12/5122175.html
-Advertisement-
Play Games

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


本文主要基於向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>標簽引入腳本代碼或者引入外部腳本

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

(3)通過動態腳本技術,即利用DOM介面創建<script>元素,並設置元素的src,然後再將元素添加進DOM中。

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

(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]);
                }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註入的腳本肯定是按照添加的順序執行;反之,如果我們採用非同步的方案,那麼添加的腳本的執行順序肯定是無法確定的。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • javascript刪除所有cookie實例代碼:一般情況下都是刪除指定的cookie,當然也可以一次性刪除所有的cookie,下麵就是一段能夠實現此功能的代碼。代碼如下:function clearCookie(){ var keys=document.cookie.match(/[^ =;].....
  • 今天在看書的過程中,又發現了自己目前對Javascript存在的一個知識模糊點:JS的作用域鏈,所以就通過查資料看書對作用域鏈相關的內容進行了學習。今天學習筆記主要有這樣幾個關鍵字:變數、參數傳遞、執行環境、變數對象、作用域鏈。 1.變數 變數需要註意的有兩點:變數聲明和複製變數值。 變數...
  • DOM 事件是 JS 中比較重要的一部分知識,所謂事件,簡單理解就是用戶對瀏覽器進行的一個操作。事件在 Web 前端領域有很重要的地位,很多重要的知識點都與事件有關,所以學好 JS 事件可以讓我們在JS的學習道路中更進一步。 1、事件流 事件流描述的是從頁面中接受事件的順序。但是 IE 和 ...
  • 1.div+css中的定位position 最主要的兩個屬性:屬性 absolute(絕對定位) relative(相對定位),有他們才造就了div+css佈局的多樣性,讓我們的網頁豐富多彩起來。首先解釋relative(相對定位),顧名思義,定位是相對的,那他是相對於什麼呢?參照物是什麼?看如下代...
  • 今天剛打個一個技術群,裡面有人問標題上的問題,嘿,我恰好遇過,現在大家至少也在用jquery1.9以上的版本,ajaxfileupload的版本早就不更新了,大家可以下載看:地址這裡,它例子里使用的Jquery是1.2的,好老呀。。。這個問題,我以前開發過程中遇過,網上說經測試(我是沒測試),是版本...
  • 幾個自學HTML的例子,僅供娛樂,並沒有其它什麼用
  • json:{ "justIn": [{ "textId": "123", "text": "Hello,geovindu", "textType": "Greeting" },{ "textId": "514", "text":"What's up?", "textType": "Question"...
  • 基於jQuery按鈕控制單排圖片切換代碼。這是一款帶有左右箭頭按鈕控制的圖片動感滾動切換特效。效果圖如下:線上預覽源碼下載實現的代碼。html代碼: ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...