HTML中的script標籤研究

Script 的堵塞(block)特性

Scripts without async or defer attributes, as well as inline scripts, are fetched and executed immediately, before the browser continues to parse the page. - MDNjavascript

the blocking nature of JavaScript, which is to say that nothing else can happen while JavaScript code is being executed. In fact, most browsers use a single process for both user interface (UI) updates and JavaScript execution, so only one can happen at any given moment in time. The longer JavaScript takes to execute, the longer it takes before the browser is free to respond to user input. - Nicholas C. Zakas「High Performance JavaScript 」html

上面引用兩段話的意思大體是,當瀏覽器解析DOM文檔時,一旦遇到 script 標籤(沒有defer 和 async 屬性)就會當即下載並執行,與此同時瀏覽器對文檔的解析將會中止,直到 script 代碼執行完成。出現這種堵塞行爲一方面是由於瀏覽器的UI渲染,交互行爲等都是單線程操做,另外一方是由於 script 裏面的代碼可能會影響到後面文檔的解析,好比下面的代碼:java

html<script type="text/javascript">
  document.write("The date is " + (new Date()).toDateString());
</script>

這個堵塞特性會嚴重的影響用戶體驗,下面是幾種優化方案:shell

  • 儘可能把腳本往文檔的後面放,以減小對文檔的堵塞,最好放在 </body> 前面。
  • 儘可能把腳本按照它們的依賴關係放在一個文件中

不過更好的方法是下面的非堵塞加載腳本(Nonblocking Scripts)的方法:跨域

1. Deferred Script (延遲腳本)

Script 有一個 defer 屬性,擁有這個屬性的script代表這個script不會修改DOM,所以這段腳本會在文檔樹所有解析完成後觸發( to be executed after the document has been parsed). 但這個屬性並不被全部的瀏覽器支持。瀏覽器

2. Dynamic Script Elements (動態腳本)

原理就是使用腳本建立 script 元素,設置 src 需爲要動態添加腳本的 URL,再把這個 script 添加到DOM中。有時咱們須要動態腳本加載完成後再執行某些操做,這就須要咱們在腳本加載完成後添加一個回調函數,這個能夠經過 script 的 onload 事件實現。下面的實現代碼:app

jsfunction loadJS(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{ 
    script.onload = function(){
      callback();
    }    
  }
  script.src = url;
  document.getElementsByTagName('head')[0].appendChild(script);
}

有時咱們須要咱們動態加載的腳本按照咱們加載的順序執行,但上面的實現並不能保證這一點,加載的腳本在下載完成後就會當即執行,而不會按照咱們定義的順序。要解決這個問題也不難,能夠參照下面的代碼:async

jsloadJS('a.js', function(){
  loadJS('b.js', function(){
    loadJS('c.js', function(){
      app.init();
    })
  })
})

當有大量的腳本須要動態添加時,這樣寫也會遇到問題;另外的解決方案是利用一些現成的庫,好比 LABjs函數

3. XMLHttpRequest Script Injection (XHR動態插入)

原理是利用XMLHttpReques(XHR)對象,動態獲取一段JS代碼,而後插入文檔。
相對其餘方法來講的一個優勢是能夠「懶執行」,也就是JS代碼已經先下載好了並無執行,能夠在須要的來執行(?)(以前的動態腳本在下載後會當即執行)。實現代碼:fetch

jsfunction xhrLoadJS (url, callback){
  var xhr = new XMLHttpRequest();
  xhr.open('get', url, 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/script';
        script.text = xhr.responseText;
        eval(xhr.responseText);  // 執行代碼
        document.body.appendChild(script);
        callback();
      }
    }
  }
  xhr.send(null);
}

缺點是不能跨域請求

參考

  1. Javascript 裝載和執行
  2. MDN Script元素
  3. Nicholas C. Zakas 所著的「High Performance JavaScript 」的第一章 "Loading and Execution"
相關文章
相關標籤/搜索