為什么要延遲加載js呢?
對於js的優化(關於js的延遲加載)的好處是有助於提高頁面加載速度,js延遲加載就是等頁面加載完成之后在加載js文件。
之所以要優化是因為HTML元素是按其在頁面中出現的次序調用的,如果用javascript來管理頁面上的元素(使用文檔對象模型dom),並且js加載於欲操作的HTML元素之前,則代碼將出錯。也就是說,我們寫了js語句來獲取DOM對象,但由於DOM結構還沒有加載完成,因此獲取到的是空對象。
舉個栗子吧~
<head> <script type="text/javascript"> var ul = document.getElementsByTagName('ul')[0]; //獲取ul var list = ul.getElementsByTagName('li'); for(var i =0;i<list.length;i++){ ul.appendChild(document.createElement('li')); } </script> </head> <body> <ul> <li>111</li> <li>222</li> <li>333</li> </ul> </body> </html>
你平時好像就是這么寫的,但是你會發現控制台會報錯:Uncaught TypeError :Cannot read property 'getElementByTagName' of undefined at...
這就是因為js加載執行於DOM結構之前,所以獲取不到。簡單的解決辦法是把<script>放在<body>后面。
js的同步加載和異步加載
同步加載,又稱阻塞模式,是我們平時使用最多的方式,也就是直接將<script>寫在<head>里。這種方式會阻止瀏覽器的后續處理,停止后續的解析,直到當前的加載完成。一般來說,同步加載是安全的,但如果我們js里設計到document內容輸出、獲取或修改DOM結構等行為,就會產生頁面阻塞代碼出錯。所以一般就會建議把<script>寫在頁面最底部,以減少頁面阻塞。(這種方式可能也是我們剛開始接觸到js優化,最常使用的一種方式。)
異步加載,又稱為非阻塞加載,在瀏覽器下載執行js的同時,還會繼續后續頁面的處理。這里也是一般面試會問到的一點,即js延遲加載的方式有哪些?
js延遲加載的六種方式
一般有六種方式;defer屬性、async屬性、動態創建dom方式、使用jquery的getScript方法、使用setTimeout延遲方法、讓js最后加載。
寫的是六種方式,實際上自己在項目中真實用到的也就是讓js最后加載。所以對這所謂的六種方式,可能僅作為一種知識儲備,當以后的項目有這種問題需求了,可以有不同的解決思路。
一、defer屬性
HTML 4.01為 <script>標簽定義了defer屬性(延遲腳本的執行)。
其用途是:表明腳本在執行時不會影響頁面的構造,瀏覽器會立即下載,但延遲執行,即腳本會被延遲到整個頁面都解析完畢之后再執行。
defer屬性只適用於外部腳本文件,只有 Internet Explorer 支持 defer 屬性。
並且defer屬性解決了async引起的腳本順序問題,使用defer屬性,腳本將按照在頁面中出現的順序加載和運行。
示例1:
//腳本1 <script defer src="js/vendor/jquery.js"></script> //腳本2 <script defer src="js/script2.js"></script> //腳本3 <script defer src="js/script3.js"></script>
上述代碼添加 defer
屬性,腳本將按照在頁面中出現的順序加載,因此可確保腳本1必定加載於腳本2和 腳本3之前,同時腳本2必定加載於腳本3之前。(補充:最近在看《高級程序設計》里面說,實際上延遲腳本並不一定會按照順序執行,也不一定會在DOMContenterLoaded事件觸發前執行,因此最好只包含一個延遲腳本)
示例2:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>defer練習</title> </head> <script type="text/javascript" defer="defer" src="example1.js"></script> <script type="text/javascript" defer="defer" src="example2.js"></script> <body> <!--這里是內容--> </body> </html>
在上面的例子中,雖然把<script>放在里<head>里,但是包含的腳本會延遲到整個頁面之后,即延遲到瀏覽器遇到</html>標簽后再執行。同時,在XHTML文檔中,要把defer屬性設置為defer="defer"
二、async屬性
HTML 5為 <script>標簽定義了async屬性。添加此屬性后,腳本和HTML將一並加載(異步),代碼將順利運行。
瀏覽器遇到async腳本時不會阻塞頁面渲染,而是直接下載然后運行。但這樣的問題是,不同腳本運行次序就無法控制,只是腳本不會阻止剩余頁面的顯示。
async屬性只適用於外部腳本文件。
示例:
//腳本1 <script async src="js/vendor/jquery.js"></script> //腳本2 <script async src="js/script2.js"></script> //腳本3 <script async src="js/script3.js"></script>
上述代碼添加async 屬性,這三者的調用順序是不確定的,腳本1可以在腳本2和腳本3之前會之后調用,這是完全不確定的。如果腳本2和腳本3需要依賴腳本1中的函數,那么不確定的調用順序會導致錯誤。(補充:異步腳本一定會在頁面的load事件前執行,但可能會在DOMContenterLoaded事件觸發之前或之后執行)
所以,當頁面的不同腳本之間彼此獨立,且不依賴於本頁面的其他任何腳本時,async是最理想的選擇。
總結:defer和async的異同點
相同:
- 加載文件時不會阻塞頁面渲染
- 對於內部的js不起作用
- 使用這兩個屬性的腳本中不能調用document.write方法
區別:
- 如果腳本無需等待頁面解析,且無依賴獨立運行,那么應使用
async
。也就是每一個async屬性的腳本都在它下載結束之后立即執行,同時會在window的load事件之前執行。 - 如果腳本需要等待解析,且依賴於其它腳本,調用這些腳本時應使用
defer
,將關聯的腳本按所需順序置於 HTML 中。
三、動態創建DOM方式
//這些代碼應被放置在</body>標簽前(接近HTML文件底部) <script type="text/javascript"> function downloadJSAtOnload(){ var element = document.createElement("script"); element.src = "defer.js"; document.body.appendChild(element); } if (window.addEventListener) //添加監聽事件 window.addEventListener("load",downloadJSAtOnload,false); //事件在冒泡階段執行 else if (window.attachEvent) window.attachEvent("onload",downloadJSAtOnload); else window.onload =downloadJSAtOnload; </script>
四、使用jquery的getScript方法
getScript() 方法通過 HTTP GET 請求載入並執行 JavaScript 文件。
語法:jQuery.getScript(url,success(response,status))
url(必寫):將要請求的 URL 字符串
success(response,status)(可選):規定請求成功后執行的回調函數。
其中的參數
response - 包含來自請求的結果數據
status - 包含請求的狀態("success", "notmodified", "error", "timeout" 或 "parsererror")
//加載並執行 test.js: $.getScript("test.js"); //加載並執行 test.js ,成功后顯示信息 $.getScript("test.js", function(){ alert("Script loaded and executed."); });
五、使用setTimeout延遲方法
<script type="text/javascript"> function A(){ $.post("/lord/login",{name:username,pwd:password},function(){ alert("Hello World!"); }) } $(function (){ setTimeout("A()",1000); //延遲1秒 }) </script>
六、讓js最后加載
將腳本元素放在文檔體的底端(</body>標簽前面),這樣腳本就可以在HTML解析完畢后加載了。但此方案的問題是,只有在所有HTML DOM加載完成后才開始腳本的加載/解析過程。對於有大量js代碼的大型網站,可能會帶來顯著的性能損耗。