一、概述
最近重新開始學習js,在第一章的一個小節裡寫到了“指令碼呼叫策略”,書上寫的這部分不多,但是發現在我之前的(筆)面試中,問到的頻率還是比較高的。自己一直習慣於直接把所有js檔案寫在head裡,後來瞭解到優化後,會把js放在最底部,但並不太懂這樣做的好處,而且其他的一些處理方式,自己也並未有過實際的操作。
在面試中對這部分的考察,主要考察的是程式的效能方面。程式的效能是一個專案不斷地追求的,通常也是專案完成後需要長期做的一件事情,為了讓使用者的體驗更好。效能優化的核心思想就是快,可以預先準備資料(如快取的使用),可以按需獲取,可以分段獲取等都是常見的優化手段。
而對於js的優化(關於js的延遲載入),主要是因為HTML元素是按其在頁面中出現的次序呼叫的,如果用javascript來管理頁面上的元素(使用文件物件模型dom),並且js載入於欲操作的HTML元素之前,則程式碼將出錯。也就是說,我們寫了js語句來獲取DOM物件,但由於DOM結構還沒有載入完成,因此獲取到的是空物件。其意義在於,這樣有助於提高頁面載入速度,js延遲載入就是等頁面載入完成之後在載入js檔案。
二、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引起的指令碼順序問題(見async的缺點),使用defer屬性,指令碼將按照在頁面中出現的順序載入和執行。
示例:
//指令碼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之前。
(二)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中的函式,那麼不確定的呼叫順序會導致錯誤。
所以,當頁面的不同指令碼之間彼此獨立,且不依賴於本頁面的其他任何指令碼時,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>
補充知識點:addEventListener與attachEvent
(四)使用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延遲方法
目的:延遲載入js程式碼,給網頁載入留出時間
<script type="text/javascript"> function A(){ $.post("/lord/login",{name:username,pwd:password},function(){ alert("Hello World!"); }) } $(function (){ setTimeout("A()",1000); //延遲1秒 }) </script>
(六)讓js最後載入
將指令碼元素放在文件體的低端,這樣指令碼就可以在HTML解析完畢後載入了。但此方案的問題是,只有在所有HTML DOM載入完成後才開始指令碼的載入/解析過程。對於有大量js程式碼的大型網站,可能會帶來顯著的效能損耗。