關於js延遲載入(非同步操作)的方式

居老師的狗子發表於2019-05-22

一、概述

   最近重新開始學習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程式碼的大型網站,可能會帶來顯著的效能損耗。

 

相關文章