怎麼樣加快JavaScript載入和執行效率

来源:http://www.cnblogs.com/hnyei/archive/2017/02/11/6389466.html
-Advertisement-
Play Games

JavaScript 在瀏覽器中的性能成為開發者所面臨的最重要的可用性問題。而這個問題又因 JavaScript 的阻塞特性變的複雜,也就是說當瀏覽器在執行 JavaScript 代碼時,不能同時做其他任何事情。本文詳細介紹瞭如何正確的載入和執行 JavaScript 代碼,從而提高其在瀏覽器中的性... ...


概覽

無論當前 JavaScript 代碼是內嵌還是在外鏈文件中,頁面的下載和渲染都必須停下來等待腳本執行完成。JavaScript 執行過程耗時越久,瀏覽器等待響應用戶輸入的時間就越長。瀏覽器在下載和執行腳本時出現阻塞的原因在於,腳本可能會改變頁面或 JavaScript 的命名空間,它們對後面頁面內容造成影響。一個典型的例子就是在頁面中使用document.write()。

JavaScript 代碼內嵌示例

<html> 
<head> 
    <title>Source Example</title> 
</head> 
<body> 
    <p> 
    <script type="text/javascript"> 
        document.write("Today is " + (new Date()).toDateString()); 
    </script> 
    </p> 
</body> 
</html>

 

當瀏覽器遇到<script>標簽時,當前 html 頁面無從獲知 JavaScript 是否會向<p> 標簽添加內容,或引入其他元素,或甚至移除該標簽。因此,這時瀏覽器會停止處理頁面,先執行 JavaScript代碼,然後再繼續解析和渲染頁面。同樣的情況也發生在使用 src 屬性載入 JavaScript的過程中,瀏覽器必須先花時間下載外鏈文件中的代碼,然後解析並執行它。在這個過程中,頁面渲染和用戶交互完全被阻塞了。

腳本位置

HTML 4 規範指出 <script> 標簽可以放在 HTML 文檔的<head>或<body>中,並允許出現多次。web 開發人員一般習慣在 <head> 中載入外鏈的 JavaScript,接著用 <link> 標簽用來載入外鏈的 CSS 文件或者其他頁面信息。

低效率腳本位置示例。

<html> 
<head> 
    <title>Source Example</title> 
    <script type="text/javascript" src="script1.js"></script> 
    <script type="text/javascript" src="script2.js"></script> 
    <script type="text/javascript" src="script3.js"></script> 
    <link rel="stylesheet" type="text/css" href="styles.css"> 
</head> 
<body> 
    <p>Hello world!</p> 
</body> 
</html>

 

然而這種常規的做法卻隱藏著嚴重的性能問題。在清單 2 的示例中,當瀏覽器解析到 <script> 標簽(第 4 行)時,瀏覽器會停止解析其後的內容,而優先下載腳本文件,並執行其中的代碼,這意味著,其後的 styles.css 樣式文件和<body>標簽都無法被載入,由於<body>標簽無法被載入,那麼頁面自然就無法渲染了。因此在該 JavaScript 代碼完全執行完之前,頁面都是一片空白。

由於腳本會阻塞頁面其他資源的下載,因此推薦將所有<script>標簽儘可能放到<body>標簽的底部,以儘量減少對整個頁面下載的影響。

推薦的代碼放置位置示例

<html> 
<head> 
    <title>Source Example</title> 
    <link rel="stylesheet" type="text/css" href="styles.css"> 
</head> 
<body> 
    <p>Hello world!</p> 
 
    <!-- Example of efficient script positioning --> 
    <script type="text/javascript" src="script1.js"></script> 
    <script type="text/javascript" src="script2.js"></script> 
    <script type="text/javascript" src="script3.js"></script> 
</body> 
</html>

這段代碼展示了在 HTML 文檔中放置<script>標簽的推薦位置。儘管腳本下載會阻塞另一個腳本,但是頁面的大部分內容都已經下載完成並顯示給了用戶,因此頁面下載不會顯得太慢。這是優化 JavaScript 的首要規則:將腳本放在底部。

組織腳本

由於每個<script>標簽初始下載時都會阻塞頁面渲染,所以減少頁麵包含的<script>標簽數量有助於改善這一情況。這不僅針對外鏈腳本,內嵌腳本的數量同樣也要限制。瀏覽器在解析 HTML 頁面的過程中每遇到一個<script>標簽,都會因執行腳本而導致一定的延時,因此最小化延遲時間將會明顯改善頁面的總體性能。

這個問題在處理外鏈 JavaScript 文件時略有不同。考慮到 HTTP 請求會帶來額外的性能開銷,因此下載單個 100Kb 的文件將比下載 5 個 20Kb 的文件更快。也就是說,減少頁面中外鏈腳本的數量將會改善性能。

通常一個大型網站或應用需要依賴數個 JavaScript 文件。您可以把多個文件合併成一個,這樣只需要引用一個<script>標簽,就可以減少性能消耗。文件合併的工作可通過離線的打包工具或者一些實時的線上服務來實現。

需要特別提醒的是,把一段內嵌腳本放在引用外鏈樣式表的<link>之後會導致頁面阻塞去等待樣式表的下載。這樣做是為了確保內嵌腳本在執行時能獲得最精確的樣式信息。因此,建議不要把內嵌腳本緊跟在<link>標簽後面。

無阻塞的腳本

減少 JavaScript 文件大小並限制 HTTP 請求數在功能豐富的 Web 應用或大型網站上並不總是可行。Web 應用的功能越豐富,所需要的 JavaScript 代碼就越多,儘管下載單個較大的 JavaScript 文件只產生一次 HTTP 請求,卻會鎖死瀏覽器的一大段時間。為避免這種情況,需要通過一些特定的技術向頁面中逐步載入 JavaScript 文件,這樣做在某種程度上來說不會阻塞瀏覽器。

無阻塞腳本的秘訣在於,在頁面載入完成後才載入 JavaScript 代碼。這就意味著在 window 對象的 onload事件觸發後再下載腳本。有多種方式可以實現這一效果。

延遲載入腳本

HTML 4 為<script>標簽定義了一個擴展屬性:deferDefer 屬性指明本元素所含的腳本不會修改 DOM,因此代碼能安全地延遲執行。defer 屬性只被 IE 4 和 Firefox 3.5 更高版本的瀏覽器所支持,所以它不是一個理想的跨瀏覽器解決方案。在其他瀏覽器中,defer 屬性會被直接忽略,因此<script>標簽會以預設的方式處理,也就是說會造成阻塞。然而,如果您的目標瀏覽器支持的話,這仍然是個有用的解決方案。

defer 屬性使用方法示例

<script type="text/javascript" src="script1.js" defer></script>

帶有 defer 屬性的<script>標簽可以放置在文檔的任何位置。對應的 JavaScript 文件將在頁面解析到<script>標簽時開始下載,但不會執行,直到 DOM 載入完成,即onload事件觸發前才會被執行。當一個帶有 defer 屬性的 JavaScript 文件下載時,它不會阻塞瀏覽器的其他進程,因此這類文件可以與其他資源文件一起並行下載。

任何帶有 defer 屬性的<script>元素在 DOM 完成載入之前都不會被執行,無論內嵌或者是外鏈腳本都是如此。清單 5 的例子展示了defer屬性如何影響腳本行為:

defer 屬性對腳本行為的影響

<html> 
<head> 
    <title>Script Defer Example</title> 
</head> 
<body> 
    <script type="text/javascript" defer> 
        alert("defer"); 
    </script> 
    <script type="text/javascript"> 
        alert("script"); 
    </script> 
    <script type="text/javascript"> 
        window.onload = function(){ 
            alert("load"); 
        }; 
    </script> 
</body> 
</html>

這段代碼在頁面處理過程中彈出三次對話框。不支持 defer 屬性的瀏覽器的彈出順序是:“defer”、“script”、“load”。而在支持 defer 屬性的瀏覽器上,彈出的順序則是:“script”、“defer”、“load”。請註意,帶有 defer 屬性的<script>元素不是跟在第二個後面執行,而是在 onload 事件被觸發前被調用。

如果您的目標瀏覽器只包括 Internet Explorer 和 Firefox 3.5,那麼 defer 腳本確實有用。如果您需要支持跨領域的多種瀏覽器,那麼還有更一致的實現方式。

HTML 5 為<script>標簽定義了一個新的擴展屬性:async。它的作用和 defer 一樣,能夠非同步地載入和執行腳本,不因為載入腳本而阻塞頁面的載入。但是有一點需要註意,在有 async 的情況下,JavaScript 腳本一旦下載好了就會執行,所以很有可能不是按照原本的順序來執行的。如果 JavaScript 腳本前後有依賴性,使用 async 就很有可能出現錯誤。

動態腳本元素

文檔對象模型(DOM)允許您使用 JavaScript 動態創建 HTML 的幾乎全部文檔內容。<script>元素與頁面其他元素一樣,可以非常容易地通過標準 DOM 函數創建:

通過標準 DOM 函數創建<script>元素

var script = document.createElement ("script"); 
   script.type = "text/javascript"; 
   script.src = "script1.js"; 
   document.getElementsByTagName("head")[0].appendChild(script);

新的<script>元素載入 script1.js 源文件。此文件當元素添加到頁面之後立刻開始下載。此技術的重點在於:無論在何處啟動下載,文件的下載和運行都不會阻塞其他頁面處理過程。您甚至可以將這些代碼放在<head>部分而不會對其餘部分的頁面代碼造成影響(除了用於下載文件的 HTTP 連接)。

當文件使用動態腳本節點下載時,返回的代碼通常立即執行(除了 Firefox 和 Opera,他們將等待此前的所有動態腳本節點執行完畢)。當腳本是“自運行”類型時,這一機制運行正常,但是如果腳本只包含供頁面其他腳本調用調用的介面,則會帶來問題。這種情況下,您需要跟蹤腳本下載完成並是否準備妥善。可以使用動態 <script> 節點發出事件得到相關信息。

Firefox、Opera, Chorme 和 Safari 3+會在<script>節點接收完成之後發出一個 onload 事件。您可以監聽這一事件,以得到腳本準備好的通知:

通過監聽 onload 事件載入 JavaScript 腳本

var script = document.createElement ("script") 
script.type = "text/javascript"; 
 
//Firefox, Opera, Chrome, Safari 3+ 
script.onload = function(){ 
    alert("Script loaded!"); 
}; 
 
script.src = "script1.js"; 
document.getElementsByTagName("head")[0].appendChild(script);

Internet Explorer 支持另一種實現方式,它發出一個 readystatechange 事件。<script>元素有一個 readyState 屬性,它的值隨著下載外部文件的過程而改變。readyState 有五種取值:

微軟文檔上說,在<script>元素的生命周期中,readyState 的這些取值不一定全部出現,但並沒有指出哪些取值總會被用到。實踐中,我們最感興趣的是“loaded”和“complete”狀態。Internet Explorer 對這兩個 readyState 值所表示的最終狀態並不一致,有時<script>元素會得到“loader”卻從不出現“complete”,但另外一些情況下出現“complete”而用不到“loaded”。最安全的辦法就是在 readystatechange 事件中檢查這兩種狀態,並且當其中一種狀態出現時,刪除 readystatechange 事件句柄(保證事件不會被處理兩次):

通過檢查 readyState 狀態載入 JavaScript 腳本

var script = document.createElement("script") 
script.type = "text/javascript"; 
 
//Internet Explorer 
script.onreadystatechange = function(){ 
     if (script.readyState == "loaded" || script.readyState == "complete"){ 
           script.onreadystatechange = null; 
           alert("Script loaded."); 
     } 
}; 
 
script.src = "script1.js"; 
document.getElementsByTagName("head")[0].appendChild(script);

大多數情況下,您希望調用一個函數就可以實現 JavaScript 文件的動態載入。下麵的函數封裝了標準實現和 IE 實現所需的功能:

通過函數進行封裝

function loadScript(url, callback){ 
    var script = document.createElement ("script") 
    script.type = "text/javascript"; 
    if (script.readyState){ //IE 
        script.onreadystatechange = function(){ 
            if (script.readyState == "loaded" || script.readyState == "complete"){ 
                script.onreadystatechange = null; 
                callback(); 
            } 
        }; 
    } else { //Others 
        script.onload = function(){ 
            callback(); 
        }; 
    } 
    script.src = url; 
    document.getElementsByTagName("head")[0].appendChild(script); 
}

此函數接收兩個參數:JavaScript 文件的 URL,和一個當 JavaScript 接收完成時觸發的回調函數。屬性檢查用於決定監視哪種事件。最後一步,設置 src 屬性,並將<script>元素添加至頁面。此 loadScript() 函數使用方法如下:

loadScript()函數使用方法

loadScript("script1.js", function(){ 
    alert("File is loaded!"); 
});

您可以在頁面中動態載入很多 JavaScript 文件,但要註意,瀏覽器不保證文件載入的順序。所有主流瀏覽器之中,只有 Firefox 和 Opera 保證腳本按照您指定的順序執行。其他瀏覽器將按照伺服器返回它們的次序下載並運行不同的代碼文件。您可以將下載操作串聯在一起以保證他們的次序,如下:

通過 loadScript()函數載入多個 JavaScript 腳本

loadScript("script1.js", function(){ 
    loadScript("script2.js", function(){ 
        loadScript("script3.js", function(){ 
            alert("All files are loaded!"); 
        }); 
    }); 
});

此代碼等待 script1.js 可用之後才開始載入 script2.js,等 script2.js 可用之後才開始載入 script3.js。雖然此方法可行,但如果要下載和執行的文件很多,還是有些麻煩。如果多個文件的次序十分重要,更好的辦法是將這些文件按照正確的次序連接成一個文件。獨立文件可以一次性下載所有代碼(由於這是非同步進行的,使用一個大文件並沒有什麼損失)。

動態腳本載入是非阻塞 JavaScript 下載中最常用的模式,因為它可以跨瀏覽器,而且簡單易用。

使用 XMLHttpRequest(XHR)對象

此技術首先創建一個 XHR 對象,然後下載 JavaScript 文件,接著用一個動態 <script> 元素將 JavaScript 代碼註入頁面。清單 12 是一個簡單的例子:

通過 XHR 對象載入 JavaScript 腳本

var xhr = new XMLHttpRequest(); 
xhr.open("get", "script1.js", true); 
xhr.onreadystatechange = function(){ 
    if (xhr.readyState == 4){ 
        if (xhr.status >= 200 && xhr.status < 300 || xhr.status == 304){ 
            var script = document.createElement ("script"); 
            script.type = "text/javascript"; 
            script.text = xhr.responseText; 
            document.body.appendChild(script); 
        } 
    } 
}; 
xhr.send(null);

此代碼向伺服器發送一個獲取 script1.js 文件的 GET 請求。onreadystatechange 事件處理函數檢查 readyState 是不是 4,然後檢查 HTTP 狀態碼是不是有效(2XX 表示有效的回應,304 表示一個緩存響應)。如果收到了一個有效的響應,那麼就創建一個新的<script>元素,將它的文本屬性設置為從伺服器接收到的 responseText 字元串。這樣做實際上會創建一個帶有內聯代碼的<script>元素。一旦新<script>元素被添加到文檔,代碼將被執行,並準備使用。

這種方法的主要優點是,您可以下載不立即執行的 JavaScript 代碼。由於代碼返回在<script>標簽之外(換句話說不受<script>標簽約束),它下載後不會自動執行,這使得您可以推遲執行,直到一切都準備好了。另一個優點是,同樣的代碼在所有現代瀏覽器中都不會引發異常。

此方法最主要的限制是:JavaScript 文件必須與頁面放置在同一個域內,不能從 CDN 下載(CDN 指"內容投遞網路(Content Delivery Network)",所以大型網頁通常不採用 XHR 腳本註入技術。

總結

減少 JavaScript 對性能的影響有以下幾種方法:

通過以上策略,可以在很大程度上提高那些需要使用大量 JavaScript 的 Web 網站和應用的實際性能。

原文來自:https://www.ibm.com/developerworks/cn/web/1308_caiys_jsload/

補充js載入函數:

function loadJs(url, callback, charset) { 
    var head = document.getElementsByTagName("head")[0]; 
    var script = document.createElement("script"); 
    if ( !!charset) script.charset = "utf-8"; 
    script.src = url; 
    script.onload = script.onreadystatechange = function() { 
        var f = script.readyState; 
        if (f && f != "loaded" && f != "complete") return; 
        script.onload = script.onreadystatechange = null; 
        head.removeChild(script) if (callback) { 
            callback() || callback 
        }; 
    }; 
    head.appendChild(script); 
}
Helloweba

首頁


HTML/CSS


Javascript/jQuery


PHP/MySQL


HTML5/移動WEB應用


前端收錄








加快JavaScript載入和執行效率

  時間:2017-01-31 21:54 標簽: js載入  


  




JavaScript 在瀏覽器中的性能成為開發者所面臨的最重要的可用性問題。而這個問題又因 JavaScript 的阻塞特性變的複雜,也就是說當瀏覽器在執行 JavaScript 代碼時,不能同時做其他任何事情。本文詳細介紹瞭如何正確的載入和執行 JavaScript 代碼,從而提高其在瀏覽器中的性能。


概覽

無論當前 JavaScript 代碼是內嵌還是在外鏈文件中,頁面的下載和渲染都必須停下來等待腳本執行完成。JavaScript 執行過程耗時越久,瀏覽器等待響應用戶輸入的時間就越長。瀏覽器在下載和執行腳本時出現阻塞的原因在於,腳本可能會改變頁面或 JavaScript 的命名空間,它們對後面頁面內容造成影響。一個典型的例子就是在頁面中使用document.write()。

JavaScript 代碼內嵌示例
<html> 
<head> 
    <title>Source Example</title> 
</head> 
<body> 
    <p> 
    <script type="text/javascript"> 
        document.write("Today is " + (new Date()).toDateString()); 
    </script> 
    </p> 
</body> 
</html>

當瀏覽器遇到<script>標簽時,當前 html 頁面無從獲知 JavaScript 是否會向<p> 標簽添加內容,或引入其他元素,或甚至移除該標簽。因此,這時瀏覽器會停止處理頁面,先執行 JavaScript代碼,然後再繼續解析和渲染頁面。同樣的情況也發生在使用 src 屬性載入 JavaScript的過程中,瀏覽器必須先花時間下載外鏈文件中的代碼,然後解析並執行它。在這個過程中,頁面渲染和用戶交互完全被阻塞了。

腳本位置

HTML 4 規範指出 <script> 標簽可以放在 HTML 文檔的<head>或<body>中,並允許出現多次。web 開發人員一般習慣在 <head> 中載入外鏈的 JavaScript,接著用 <link> 標簽用來載入外鏈的 CSS 文件或者其他頁面信息。

低效率腳本位置示例
<html> 
<head> 
    <title>Source Example</title> 
    <script type="text/javascript" src="script1.js"></script> 
    <script type="text/javascript" src="script2.js"></script> 
    <script type="text/javascript" src="script3.js"></script> 
    <link rel="stylesheet" type="text/css" href="styles.css"> 
</head> 
<body> 
    <p>Hello world!</p> 
</body> 
</html>

然而這種常規的做法卻隱藏著嚴重的性能問題。在清單 2 的示例中,當瀏覽器解析到 <script> 標簽(第 4 行)時,瀏覽器會停止解析其後的內容,而優先下載腳本文件,並執行其中的代碼,這意味著,其後的 styles.css 樣式文件和<body>標簽都無法被載入,由於<body>標簽無法被載入,那麼頁面自然就無法渲染了。因此在該 JavaScript 代碼完全執行完之前,頁面都是一片空白。

由於腳本會阻塞頁面其他資源的下載,因此推薦將所有<script>標簽儘可能放到<body>標簽的底部,以儘量減少對整個頁面下載的影響。

推薦的代碼放置位置示例
<html> 
<head> 
    <title>Source Example</title> 
    <link rel="stylesheet" type="text/css" href="styles.css"> 
</head> 
<body> 
    <p>Hello world!</p> 
 
    <!-- Example of efficient script positioning --> 
    <script type="text/javascript" src="script1.js"></script> 
    <script type="text/javascript" src="script2.js"></script> 
    <script type="text/javascript" src="script3.js"></script> 
</body> 
</html>

這段代碼展示了在 HTML 文檔中放置<script>標簽的推薦位置。儘管腳本下載會阻塞另一個腳本,但是頁面的大部分內容都已經下載完成並顯示給了用戶,因此頁面下載不會顯得太慢。這是優化 JavaScript 的首要規則:將腳本放在底部。

組織腳本

由於每個<script>標簽初始下載時都會阻塞頁面渲染,所以減少頁麵包含的<script>標簽數量有助於改善這一情況。這不僅針對外鏈腳本,內嵌腳本的數量同樣也要限制。瀏覽器在解析 HTML 頁面的過程中每遇到一個<script>標簽,都會因執行腳本而導致一定的延時,因此最小化延遲時間將會明顯改善頁面的總體性能。

這個問題在處理外鏈 JavaScript 文件時略有不同。考慮到 HTTP 請求會帶來額外的性能開銷,因此下載單個 100Kb 的文件將比下載 5 個 20Kb 的文件更快。也就是說,減少頁面中外鏈腳本的數量將會改善性能。

通常一個大型網站或應用需要依賴數個 JavaScript 文件。您可以把多個文件合併成一個,這樣只需要引用一個<script>標簽,就可以減少性能消耗。文件合併的工作可通過離線的打包工具或者一些實時的線上服務來實現。

需要特別提醒的是,把一段內嵌腳本放在引用外鏈樣式表的<link>之後會導致頁面阻塞去等待樣式表的下載。這樣做是為了確保內嵌腳本在執行時能獲得最精確的樣式信息。因此,建議不要把內嵌腳本緊跟在<link>標簽後面。

無阻塞的腳本

減少 JavaScript 文件大小並限制 HTTP 請求數在功能豐富的 Web 應用或大型網站上並不總是可行。Web 應用的功能越豐富,所需要的 JavaScript 代碼就越多,儘管下載單個較大的 JavaScript 文件只產生一次 HTTP 請求,卻會鎖死瀏覽器的一大段時間。為避免這種情況,需要通過一些特定的技術向頁面中逐步載入 JavaScript 文件,這樣做在某種程度上來說不會阻塞瀏覽器。

無阻塞腳本的秘訣在於,在頁面載入完成後才載入 JavaScript 代碼。這就意味著在 window 對象的 onload事件觸發後再下載腳本。有多種方式可以實現這一效果。

延遲載入腳本

HTML 4 為<script>標簽定義了一個擴展屬性:defer。Defer 屬性指明本元素所含的腳本不會修改 DOM,因此代碼能安全地延遲執行。defer 屬性只被 IE 4 和 Firefox 3.5 更高版本的瀏覽器所支持,所以它不是一個理想的跨瀏覽器解決方案。在其他瀏覽器中,defer 屬性會被直接忽略,因此<script>標簽會以預設的方式處理,也就是說會造成阻塞。然而,如果您的目標瀏覽器支持的話,這仍然是個有用的解決方案。

defer 屬性使用方法示例
<script type="text/javascript" src="script1.js" defer></script>

帶有 defer 屬性的<script>標簽可以放置在文檔的任何位置。對應的 JavaScript 文件將在頁面解析到<script>標簽時開始下載,但不會執行,直到 DOM 載入完成,即onload事件觸發前才會被執行。當一個帶有 defer 屬性的 JavaScript 文件下載時,它不會阻塞瀏覽器的其他進程,因此這類文件可以與其他資源文件一起並行下載。

任何帶有 defer 屬性的<script>元素在 DOM 完成載入之前都不會被執行,無論內嵌或者是外鏈腳本都是如此。清單 5 的例子展示了defer屬性如何影響腳本行為:

defer 屬性對腳本行為的影響
<html> 
<head> 
    <title>Script Defer Example</title> 
</head> 
<body> 
    <script type="text/javascript" defer> 
        alert("defer"); 
    </script> 
    <script type="text/javascript"> 
        alert("script"); 
    </script> 
    <script type="text/javascript"> 
        window.onload = function(){ 
            alert("load"); 
        }; 
    </script> 
</body> 
</html>

這段代碼在頁面處理過程中彈出三次對話框。不支持 defer 屬性的瀏覽器的彈出順序是:“defer”、“script”、“load”。而在支持 defer 屬性的瀏覽器上,彈出的順序則是:“script”、“defer”、“load”。請註意,帶有 defer 屬性的<script>元素不是跟在第二個後面執行,而是在 onload 事件被觸發前被調用。

如果您的目標瀏覽器只包括 Internet Explorer 和 Firefox 3.5,那麼 defer 腳本確實有用。如果您需要支持跨領域的多種瀏覽器,那麼還有更一致的實現方式。

HTML 5 為<script>標簽定義了一個新的擴展屬性:async。它的作用和 defer 一樣,能夠非同步地載入和執行腳本,不因為載入腳本而阻塞頁面的載入。但是有一點需要註意,在有 async 的情況下,JavaScript 腳本一旦下載好了就會執行,所以很有可能不是按照原本的順序來執行的。如果 JavaScript 腳本前後有依賴性,使用 async 就很有可能出現錯誤。

動態腳本元素

文檔對象模型(DOM)允許您使用 JavaScript 動態創建 HTML 的幾乎全部文檔內容。<script>元素與頁面其他元素一樣,可以非常容易地通過標準 DOM 函數創建:

通過標準 DOM 函數創建<script>元素
var script = document.createElement ("script"); 
   script.type = "text/javascript"; 
   script.src = "script1.js"; 
   document.getElementsByTagName("head")[0].appendChild(script);

新的<script>元素載入 script1.js 源文件。此文件當元素添加到頁面之後立刻開始下載。此技術的重點在於:無論在何處啟動下載,文件的下載和運行都不會阻塞其他頁面處理過程。您甚至可以將這些代碼放在<head>部分而不會對其餘部分的頁面代碼造成影響(除了用於下載文件的 HTTP 連接)。

當文件使用動態腳本節點下載時,返回的代碼通常立即執行(除了 Firefox 和 Opera,他們將等待此前的所有動態腳本節點執行完畢)。當腳本是“自運行”類型時,這一機制運行正常,但是如果腳本只包含供頁面其他腳本調用調用的介面,則會帶來問題。這種情況下,您需要跟蹤腳本下載完成並是否準備妥善。可以使用動態 <script> 節點發出事件得到相關信息。

Firefox、Opera, Chorme 和 Safari 3+會在<script>節點接收完成之後發出一個 onload 事件。您可以監聽這一事件,以得到腳本準備好的通知:

通過監聽 onload 事件載入 JavaScript 腳本
var script = document.createElement ("script") 
script.type = "text/javascript"; 
 
//Firefox, Opera, Chrome, Safari 3+ 
script.onload = function(){ 
    alert("Script loaded!"); 
}; 
 
script.src = "script1.js"; 
document.getElementsByTagName("head")[0].appendChild(script);

Internet Explorer 支持另一種實現方式,它發出一個 readystatechange 事件。<script>元素有一個 readyState 屬性,它的值隨著下載外部文件的過程而改變。readyState 有五種取值:

微軟文檔上說,在<script>元素的生命周期中,readyState 的這些取值不一定全部出現,但並沒有指出哪些取值總會被用到。實踐中,我們最感興趣的是“loaded”和“complete”狀態。Internet Explorer 對這兩個 readyState 值所表示的最終狀態並不一致,有時<script>元素會得到“loader”卻從不出現“complete”,但另外一些情況下出現“complete”而用不到“loaded”。最安全的辦法就是在 readystatechange 事件中檢查這兩種狀態,並且當其中一種狀態出現時,刪除 readystatechange 事件句柄(保證事件不會被處理兩次):

通過檢查 readyState 狀態載入 JavaScript 腳本
var script = document.createElement("script") 
script.type = "text/javascript"; 
 
//Internet Explorer 
script.onreadystatechange = function(){ 
     if (script.readyState == "loaded" || script.readyState == "complete"){ 
           script.onreadystatechange = null; 
           alert("Script loaded."); 
     } 
}; 
 
script.src = "script1.js"; 
document.getElementsByTagName("head")[0].appendChild(script);

大多數情況下,您希望調用一個函數就可以實現 JavaScript 文件的動態載入。下麵的函數封裝了標準實現和 IE 實現所需的功能:

通過函數進行封裝
function loadScript(url, callback){ 
    var script = document.createElement ("script") 
    script.type = "text/javascript"; 
    if (script.readyState){ //IE 
        script.onreadystatechange = function(){ 
            if (script.readyState == "loaded" || script.readyState == "complete"){ 
                script.onreadystatechange = null; 
                callback(); 
            } 
        }; 
    } else { //Others 
        script.onload = function(){ 
            callback(); 
        }; 
    } 
    script.src = url; 
    document.getElementsByTagName("head")[0].appendChild(script); 
}

此函數接收兩個參數:JavaScript 文件的 URL,和一個當 JavaScript 接收完成時觸發的回調函數。屬性檢查用於決定監視哪種事件。最後一步,設置 src 屬性,並將<script>元素添加至頁面。此 loadScript() 函數使用方法如下:

loadScript()函數使用方法
loadScript("script1.js", function(){ 
    alert("File is loaded!"); 
});

您可以在頁面中動態載入很多 JavaScript 文件,但要註意,瀏覽器不保證文件載入的順序。所有主流瀏覽器之中,只有 Firefox 和 Opera 保證腳本按照您指定的順序執行。其他瀏覽器將按照伺服器返回它們的次序下載並運行不同的代碼文件。您可以將下載操作串聯在一起以保證他們的次序,如下:

通過 loadScript()函數載入多個 JavaScript 腳本
loadScript("script1.js", function(){ 
    loadScript("script2.js", function(){ 
        loadScript("script3.js", function(){ 
            alert("All files are loaded!"); 
        }); 
    }); 
});

此代碼等待 script1.js 可用之後才開始載入 script2.js,等 script2.js 可用之後才開始載入 script3.js。雖然此方法可行,但如果要下載和執行的文件很多,還是有些麻煩。如果多個文件的次序十分重要,更好的辦法是將這些文件按照正確的次序連接成一個文件。獨立文件可以一次性下載所有代碼(由於這是非同步進行的,使用一個大文件並沒有什麼損失)。

動態腳本載入是非阻塞 JavaScript 下載中最常用的模式,因為它可以跨瀏覽器,而且簡單易用。

使用 XMLHttpRequest(XHR)對象

此技術首先創建一個 XHR 對象,然後下載 JavaScript 文件,接著用一個動態 <script> 元素將 JavaScript 代碼註入頁面。清單 12 是一個簡單的例子:

通過 XHR 對象載入 JavaScript 腳本
var xhr = new XMLHttpRequest(); 
xhr.open("get", "script1.js", true); 
xhr.onreadystatechange = function(){ 
    if (xhr.readyState == 4){ 
        if (xhr.status >= 200 && xhr.status < 300 || xhr.status == 304){ 
            var script = document.createElement ("script"); 
            script.type = "text/javascript"; 
            script.text = xhr.responseText; 
            document.body.appendChild(script); 
        } 
    } 
}; 
xhr.send(null);

此代碼向伺服器發送一個獲取 script1.js 文件的 GET 請求。onreadystatechange 事件處理函數檢查 readyState 是不是 4,然後檢查 HTTP 狀態碼是不是有效(2XX 表示有效的回應,304 表示一個緩存響應)。如果收到了一個有效的響應,那麼就創建一個新的<script>元素,將它的文本屬性設置為從伺服器接收到的 responseText 字元串。這樣做實際上會創建一個帶有內聯代碼的<script>元素。一旦新<script>元素被添加到文檔,代碼將被執行,並準備使用。

這種方法的主要優點是,您可以下載不立即執行的 JavaScript 代碼。由於代碼返回在<script>標簽之外(換句話說不受<script>標簽約束),它下載後不會自動執行,這使得您可以推遲執行,直到一切都準備好了。另一個優點是,同樣的代碼在所有現代瀏覽器中都不會引發異常。

此方法最主要的限制是:JavaScript 文件必須與頁面放置在同一個域內,不能從 CDN 下載(CDN 指"內容投遞網路(Content Delivery Network)",所以大型網頁通常不採用 XHR 腳本註入技術。

總結

減少 JavaScript 對性能的影響有以下幾種方法:

通過以上策略,可以在很大程度上提高那些需要使用大量 JavaScript 的 Web 網站和應用的實際性能。

原文來自:https://www.ibm.com/developerworks/cn/web/1308_caiys_jsload/

補充js載入函數:
function loadJs(url, callback, charset) { 
    var head = document.getElementsByTagName("head")[0]; 
    var script = document.createElement("script"); 
    if ( !!charset) script.charset = "utf-8"; 
    script.src = url; 
    script.onload = script.onreadystatechange = function() { 
        var f = script.readyState; 
        if (f && f != "loaded" && f != "complete") return; 
        script.onload = script.onreadystatechange = null; 
        head.removeChild(script) if (callback) { 
            callback() || callback 
        }; 
    }; 
    head.appendChild(script); 
}
// js同步載入 
function getScripts(i, linkArray, fn) { 
    env || getEnv(); 
    var script = document.createElement('script'); 
    script.type = 'text/javascript'; 
    script.src = linkArray[i]; 
    var head = document.head || document.getElementsByTagName('head')[0]; 
    head.appendChild(script); 
 
    if (env.ie && 'onreadystatechange' in script && !('draggable' in script)){ //ie瀏覽器使用以下方式載入 
        script.onreadystatechange = function () { 
          if (/loaded|complete/.test(script.readyState)) { 
            script.onreadystatechange = null; 
            if(i === linkArray.length-1) { 
                if (fn) { 
                    fn(); 
                } 
            } else { 
                getScripts(++i, linkArray, fn); 
            } 
          } 
        }; 
    }else{ 
        script.onload = function() { 
            if(i === linkArray.length-1) { 
                if (fn) { 
                    fn(); 
                } 
            } else { 
                getScripts(++i, linkArray, fn); 
            } 
        }; 
    } 
}
// js存在依賴關係 依次載入 
getScripts(0, [ 
    'http://caibaojian.com/demo/base.js', 
    'http://caibaojian.com/demo/reset.js'], function() { 
     alert('callback'); 
});

 


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

-Advertisement-
Play Games
更多相關文章
  • 懶載入技術是現在許多大型網站的都使用的提高網站性能的方式,它的核心思想是當用戶想看頁面某個區域時,再載入該區域的數據。這在一定程度上減輕了伺服器端的壓力,也加快了頁面的呈現速度。 其實國內很多網站都用到了懶載入技術,比如國內兩個大型電商網站的頁面都運用到了這項技術: 天貓首頁: 京東商品列表頁: 最 ...
  • 前言: 關於前端的第二篇博客,會寫關於css的,內容比較基礎。寫完這篇博客,會做一個類似美樂樂傢具的界面。good luck to me~ 一、css-引用樣式 標簽上設置style屬性: 引用樣式的三種方式: 第一種:在<body>直接定義與引用樣式 第二種: 在<head>頭部加上下麵代碼(類似 ...
  • 兩個部分,一個是如題的直接上代碼;二是外一則的小體會。 2.js裡面的this對象 var A={a:function(e){alert(e)},b:function(){console.log(this);this.a(1)}} A.b();//直接這樣調用不報錯,控制台裡面看到this是一個有a ...
  • 頁面DOM里的每個節點上都有一個classList對象,程式員可以使用裡面的方法新增、刪除、修改節點上的CSS類。使用classList,程式員還可以用它來判斷某個節點是否被賦予了某個CSS類。 添加類(add) 為 <div> 元素添加多個類: 移除類(remove) 使用remove方法,你可以 ...
  • 轉載網址:http://www.jb51.net/css/529846.html 前言 其實不管是三欄佈局還是兩欄佈局都是我們在平時項目里經常使用的,也許你不知道什麼事三欄佈局什麼是兩欄佈局但實際已經在用,或許你知道三欄佈局的一種或兩種方法,但實際操作中也只會依賴那某一種方法,本文具體的介紹了三欄布 ...
  • 如果你是一個人在自學前端開發,或者是對前端開發有比較濃厚的興趣正想踏入前端領域,只要你在前端自學路上遇到了自己無法解決的技術難題,那麼儘管將你的疑惑交給我的小伙伴兒們吧,我們都是一群在前端自學路上摸爬滾打的有志青年,希望你可以來和我們共同交流。同時也希望你能獻出自己的一份力,幫助我的小伙伴兒們解決他 ...
  • for...in主要用於對數組和對象的屬性進行遍歷。for ... in 迴圈中的代碼每執行一次,就會對數組的元素或者對象的屬性進行一次操作。 語法:for (variable in object) {...} 對數組操作 可以發現在for in函數中變數以字元串的形式出現,這時候在函數中操作a[x ...
  • JS腳本(jQuery)為圖片加水印效果預覽:http://hovertree.com/texiao/jquery/94/本功能使用HTML5實現,可為圖片加上文字水印,可設置文字,設置顏色,位置等,加水印的圖片需和網頁在同個功能變數名稱下。完整代碼如下: 源碼下載:http://hovertree.com ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...