一次加載javascript的思考(defer,async)

最近在作我的項目中登錄界面的時候,須要加一我的機驗證也就是驗證碼的功能,和朋友商量再三因爲時間有限,便選擇引入第三方的驗證碼。使用過程當中發現,該第三方驗證碼的實現邏輯基本是先編寫指定id的HTML標籤,而後加載該第三方的js,由其本身渲染而成,所以借這個機會,再次學習總結一下傳統的<script>標籤加載 JavaScript 腳本相關知識,主要是defer,async屬性。javascript

HTML頁面解析過程

爲了更好的理解腳本加載的知識,首先簡單瞭解一下HTML頁面處理過程:(關於解析,請看下文script的內容)html

  1. 瀏覽器經過HTTP協議請求服務器,獲取HMTL文檔並開始從上到下解析,構建DOM;
  2. 在構建DOM過程當中,若是遇到外聯的樣式聲明和腳本聲明,則暫停文檔渲染,建立新的網絡鏈接,並開始下載樣式文件和腳本文件;
  3. 樣式文件下載完成後,構建CSSDOM;腳本文件下載完成後,解釋並執行,而後繼續構建DOM
  4. 完成文檔解析後,將DOM和CSSDOM進行關聯和映射,最後將視圖渲染到瀏覽器窗口

在這個過程當中,普通腳本文件的下載和執行會阻塞文檔的渲染,若是控制得很差,在用戶體驗上就會形成必定程度的影響
所以,大多提倡將腳本加載放在頁面末尾,通常是</body>處:前端

雅虎軍規第18條:把腳本放在底部

腳本加載實例:java

<!-- 頁面內嵌的腳本 -->
<script type="application/javascript">
  // module code
</script>

<!-- 外部腳本 -->
<script type="application/javascript" src="path/to/my.js">
</script>

普通腳本

沒有 defer 或 async修飾,瀏覽器會當即加載並執行指定的腳本,「當即」指的是在渲染該 script 標籤後面的文檔元素以前,也就是說不等待後續載入的文檔元素,讀到就加載並執行。
若是解析遇到多個<script>標籤,依次加載順序執行。
*此操做會阻止後續文檔元素的解析和渲染,可是這裏有一個預解析的概念
(Webkit 和 Firefox 都進行了這項優化。在執行腳本時,其餘線程會解析文檔的其他部分,找出並加載須要經過網絡加載的其餘資源。經過這種方式,資源能夠在並行鏈接上加載,從而提升整體速度。請注意,預解析器不會修改 DOM 樹,而是將這項工做交由主解析器處理;預解析器只會解析外部資源(例如外部腳本、樣式表和圖片)的引用。)*segmentfault

defer腳本

若是script標籤設置了該屬性,則瀏覽器會異步的下載該文件而且不會影響到後續DOM的渲染;
若是有多個設置了defer的script標籤存在,則會按照順序執行全部的script;
defer腳本會在文檔渲染完畢後,DOMContentLoaded事件調用前執行。
注意:瀏覽器

*在現實當中,延遲腳本並不必定會按照順序執行,也不必定會在 DOMContentLoaded事件觸發前執行,所以最好 只包含一個延遲腳本。 澤卡斯(Zakas. Nicholas C.). JavaScript高級程序設計(第3版) (圖靈程序設計叢書)

async腳本

async的設置,會使得script腳本異步的加載並在容許的狀況下執行,也就是說加載和渲染後續文檔元素的過程將和script加載並行進行
async的執行,並不會按着script在頁面中的順序來執行,而是誰先加載完誰執行。服務器

上述三種總結爲一張圖片(出處見參考)

bVcQV0

關於項目中的應用

因爲是模塊化開發,在此採用的是再模塊內經過動態方式加載第三方的驗證碼js,主要代碼以下。網絡

function load (el, src, callback) {
  if (!src) {
    return;
  }
  // _verifyExist(src);
  let scriptHeat = document.createElement('script');
  scriptHeat.type = 'text/javascript';
  scriptHeat.src = src;
  scriptHeat.defer = true;
  /* 爲保證兼容性,在此對回調包裝, */
  isFunction(callback) && addOnloadHandler(scriptHeat, callback);
  el.appendChild(scriptHeat);
}
function isFunction (fn) {
  return Object.prototype.toString.call(fn) === '[object Function]';
}
function addOnloadHandler (el, callback) {
  el.onload = el.onreadystatechange = function () {
    if (!this.readyState || // 這是FF的判斷語句,由於ff下沒有readyState這人值,IE的readyState確定有值
      this.readyState === 'loaded' || this.readyState === 'complete' // 這是IE的判斷語句
    ) {
      callback();
    }
  };
}

爲保證該第三方庫執行時有其渲染的元素,因此設置爲defer。可能引起問題是若是網絡慢或其餘緣由會致使該驗證控件呈現較慢(暫時未遇到),因此項目中也加了遮罩處理。app

參考
defer和async的區別
詳解defer和async的原理及應用
MDN
前端文摘:深刻解析瀏覽器的幕後工做原理異步

相關文章
相關標籤/搜索