首頁 > web前端 > js教程 > 主體

幾個JavaScript異步加載相關問題

小云云
發布: 2018-02-22 09:06:54
原創
1595 人瀏覽過

預設的js是同步載入的,這裡的“載入”可以理解成是解析、執行,而不是“下載”,在最新版本的瀏覽器中,瀏覽器對於程式碼請求的資源都是瀑布式的加載,而不是阻塞式的,但是js的執行總是阻塞的。這會引起什麼問題呢?如果我的index頁面要載入一些js,但是其中的某個請求遲遲得不到回應,於是阻塞了後面的js程式碼的執行(同步載入),同時頁面渲染也不能繼續(如果js引入是在head標籤後)。


<script type="text/javascript" src=&#39;http://china-addthis.googlecode.com/svn/trunk/addthis.js&#39;></script>
<script type="text/javascript" src=&#39;http://libs.baidu.com/jquery/2.0.0/jquery.min.js&#39;></script>
this is a test
登入後複製

例如上面的這段程式碼,儲存為index.html文件,頁面的主體是一個簡單的字串,但是程式碼執行後頁面遲遲都是空白,為何?因為請求的js遲遲無法載入(可能由於Google被牆等原因),於是阻塞了後面的程式碼的執行,頁面得不到渲染。可能你會提議,把js程式碼放到前不就能先渲染頁面了!好方法,我們嘗試將js放後面:


this is a test
<script type="text/javascript" src=&#39;http://china-addthis.googlecode.com/svn/trunk/addthis.js&#39;></script>
<script type="text/javascript" src=&#39;http://libs.baidu.com/jquery/2.0.0/jquery.min.js&#39;></script>
登入後複製

頁面瞬間被渲染,「this is a test」也很快出現在前台,世界似乎平靜了,可是:


this is a test
<script type="text/javascript" src=&#39;http://china-addthis.googlecode.com/svn/trunk/addthis.js&#39;></script>
<script type="text/javascript" src=&#39;http://libs.baidu.com/jquery/2.0.0/jquery.min.js&#39;></script>
登入後複製

在前面程式碼的基礎上簡單加了一段程式碼,但是"hello world"遲遲無法在控制台輸出,顯然前面的js請求阻塞了後面程式碼的載入,我們恍然大悟,改變js的載入位置只能改變頁面的渲染,然而對於js的載入並沒有什麼卵用,js還是會阻塞。

實作js非同步載入

我們的要求似乎很簡單,能在頁面載入的同時,在控制台輸出字串即可,再講的通俗一點,就是在請求第一段谷歌提供的js的同時,繼續執行下面的js,也就是實作js的非同步載入。

最常見的做法是動態產生script標籤:


<body>
 this is a test
 <script type="text/javascript">
  ~function() {
   var s = document.createElement(&#39;script&#39;);
   s.src = &#39;http://china-addthis.googlecode.com/svn/trunk/addthis.js&#39;;
   document.body.appendChild(s);
  }();
 </script>
 <script type="text/javascript" src=&#39;http://libs.baidu.com/jquery/2.0.0/jquery.min.js&#39;></script>
 <script type="text/javascript">
  console.log(&#39;hello world&#39;);
 </script>
</body>
登入後複製

但還是有點問題,這種載入方式在載入執行完之前會阻止onload 事件的觸發,而現在很多頁面的程式碼在onload 時還要執行額外的渲染工作等,所以還是會阻塞部分頁面的初始化處理:


<body>
 this is a test
 <script type="text/javascript">
  ~function() {
   // function async_load() {
    var s = document.createElement(&#39;script&#39;);
    s.src = &#39;http://china-addthis.googlecode.com/svn/trunk/addthis.js&#39;;
    document.body.appendChild(s);
   // }
   // window.addEventListener(&#39;load&#39;, async_load, false);
  }();

  window.onload = function() {
   var txt = document.createTextNode(&#39; hello world&#39;);
   document.body.appendChild(txt);
  };
 </script>
 <script type="text/javascript" src=&#39;http://libs.baidu.com/jquery/2.0.0/jquery.min.js&#39;></script>
</body>
登入後複製

例如上面的程式碼不能很好地渲染”hello world”,我們只需將註解去掉就可以了,讓Google提供的js在onload 時才開始非同步載入。這樣就解決了阻塞 onload 事件觸發的問題。

補充DOMContentLoaded 與 OnLoad 事件 DOMContentLoaded : 頁面(document)已經解析完成,頁面中的dom元素已經可用。但是頁面中引用的圖片、subframe可能還沒載入完。 OnLoad:頁面的所有資源都加載完畢(包括圖片)。瀏覽器的載入進度在這時才停止。這兩個時間點將頁面載入的timeline分成了三個階段。

以上似乎能較好解決這個問題,但html5提供了更簡單的方法,async屬性!


this is a test
<script type="text/javascript" src=&#39;http://china-addthis.googlecode.com/svn/trunk/addthis.js&#39; async=&#39;async&#39;></script>
<script type="text/javascript" src=&#39;http://libs.baidu.com/jquery/2.0.0/jquery.min.js&#39;></script>
<script type="text/javascript">
 console.log(&#39;hello world&#39;);
</script>
登入後複製

async是html5的新屬性,async 屬性規定一旦腳本可用,則會非同步執行(一旦下載完成就會立刻執行)。

要注意的是async 屬性只適用於外部腳本(只有在使用src 屬性時)

defer屬性常常和async一起提起:


this is a test
<script type="text/javascript" src=&#39;http://china-addthis.googlecode.com/svn/trunk/addthis.js&#39; defer=&#39;defer&#39;></script>
<script type="text/javascript" src=&#39;http://libs.baidu.com/jquery/2.0.0/jquery.min.js&#39;></script>
<script type="text/javascript">
 console.log(&#39;hello world&#39;);
</script>
登入後複製

似乎實現效果差不多,但真的一樣嗎?我們來看看defer屬性的定義。

以前的defer只支援ie的hack,現在html5的出現開始全面支援defer。 defer 屬性規定當頁面已完成載入後,才會執行腳本。 defer 屬性僅適用於外部腳本(只有在使用 src 屬性時)。 ps:ie支持的defer似乎並非如此,因為對ie無感,不深究,有興趣的可以去查閱相關資料。

既然async和defer常常一起出現,那麼辨識一下吧!

如果沒有async和defer屬性(賦值為true,下同),那麼瀏覽器會立即執行目前的js腳本,阻塞後面的腳本;如果有async屬性,載入和渲染後續文件元素的過程將會和目前js的載入與執行並行進行(非同步);如果有defer屬性,那麼載入後續文件元素的過程將會和script.js 的載入並行進行(非同步),但是script.js 的執行要在所有元素( DOM)解析完成之後,DOMContentLoaded 事件觸發前完成。

來看一張網路上盜的圖:

藍色線代表網路讀取,紅色線代表執行時間,這兩個都是針對腳本的;綠線代表HTML 解析。

此圖告訴我們以下幾個要點(摘自defer和async的區別):

  1. #defer 和async 在網路讀取(下載)這塊兒是一樣的,都是非同步的(相較於HTML 解析)

  2. 它倆的差別在於腳本下載完之後何時執行,顯然defer 是最接近我們對於應用程式腳本載入和執行的要求的

  3. 关于 defer,此图未尽之处在于它是按照加载顺序执行脚本的,这一点要善加利用

  4. async 则是一个乱序执行的主,反正对它来说脚本的加载和执行是紧紧挨着的,所以不管你声明的顺序如何,只要它加载完了就会立刻执行

  5. 仔细想想,async 对于应用脚本的用处不大,因为它完全不考虑依赖(哪怕是最低级的顺序执行),不过它对于那些可以不依赖任何脚本或不被任何脚本依赖的脚本来说却是非常合适的,最典型的例子:Google Analytics

但是在我看来(以下个人理解,如有出入还望指出),defer在异步加载上的应用并不会比async广。async的英文解释是异步,该属性作用在脚本上,使得脚本加载(下载)完后随即开始执行,和动态插入script标签作用类似(async只支持h5,后者能兼容浏览器);而defer的英文解释是延迟,作用也和字面解释类似,延迟脚本的执行,使得dom元素加载完后才开始有序执行脚本,因为有序,所以会带来另一个问题:


this is a test
<script type="text/javascript" src=&#39;http://china-addthis.googlecode.com/svn/trunk/addthis.js&#39; defer=&#39;defer&#39;></script>
<script type="text/javascript" src=&#39;http://libs.baidu.com/jquery/2.0.0/jquery.min.js&#39; defer=&#39;defer&#39;></script>
<script type="text/javascript" src=&#39;index.js&#39; defer=&#39;defer&#39;></script>
console.log(&#39;hello world&#39;);
登入後複製

如果执行这段代码,控制台的“hello world”也会迟迟得不到结果。所以我觉得还是async好用,如果要考虑依赖的话,可以选择requirejs、seajs等模块加载器。

总结

JavaScript的异步加载还有一些方式,比如:AJAX eval(使用AJAX得到脚本内容,然后通过eval(xmlhttp.responseText)来运行脚本)、iframe方式等。

相关推荐:

Angular结合zTree异步加载节点数据实例分享

JavaScript文件的同步和异步加载的实现代码

使用DataTable插件实现异步加载数据详解


以上是幾個JavaScript異步加載相關問題的詳細內容。更多資訊請關注PHP中文網其他相關文章!

相關標籤:
來源:php.cn
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
最新問題
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板
關於我們 免責聲明 Sitemap
PHP中文網:公益線上PHP培訓,幫助PHP學習者快速成長!