默認的js是同步加載的,這裡的“加載”可以理解成是解析、執行,而不是“下載”,在最新版本的浏覽器中,浏覽器對於代碼請求的資源都是瀑布式的加載,而不是阻塞式的,但是js的執行總是阻塞的。這會引起什麼問題呢?如果我的index頁面要加載一些js,但是其中的某個請求遲遲得不到響應,於是阻塞了後面的js代碼的執行(同步加載),同時頁面渲染也不能繼續(如果js引入是在head標簽後)。
<script type="text/javascript" src='http://china-addthis.googlecode.com/svn/trunk/addthis.js'></script> <script type="text/javascript" src='http://libs.baidu.com/jquery/2.0.0/jquery.min.js'></script> this is a test
比如上面的這段代碼,保存為index.html文件,頁面的主體是一個簡單的字符串,但是代碼執行後頁面遲遲都是空白,為何?因為請求的js遲遲無法加載(可能由於谷歌被牆等原因),於是阻塞了後面的代碼的執行,頁面得不到渲染。可能你會提議,把js代碼放到</body>前不就能先渲染頁面了!好方法,我們嘗試著將js放後面:
this is a test <script type="text/javascript" src='http://china-addthis.googlecode.com/svn/trunk/addthis.js'></script> <script type="text/javascript" src='http://libs.baidu.com/jquery/2.0.0/jquery.min.js'></script>
頁面瞬間被渲染,“this is a test"也很快出現在前台,世界似乎平靜了,可是:
this is a test <script type="text/javascript" src='http://china-addthis.googlecode.com/svn/trunk/addthis.js'></script> <script type="text/javascript" src='http://libs.baidu.com/jquery/2.0.0/jquery.min.js'></script> <script type="text/javascript"> console.log('hello world'); </script>
在前面代碼的基礎上簡單加了一段代碼,但是"hello world"遲遲無法在控制台輸出,顯然前面的js請求阻塞了後面代碼的加載,我們恍然大悟,改變js的加載位置只能改變頁面的渲染,然而對於js的加載並沒有什麼卵用,js還是會阻塞。
我們的要求似乎很簡單,能在頁面加載的同時,在控制台輸出字符串即可,再講的通俗一點,就是在請求第一段谷歌提供的js的同時,繼續執行下面的js,也就是實現js的異步加載。
最常見的做法是動態生成script標簽:
<body> this is a test <script type="text/javascript"> ~function() { var s = document.createElement('script'); s.src = 'http://china-addthis.googlecode.com/svn/trunk/addthis.js'; document.body.appendChild(s); }(); </script> <script type="text/javascript" src='http://libs.baidu.com/jquery/2.0.0/jquery.min.js'></script> <script type="text/javascript"> console.log('hello world'); </script> </body>
但是還是有點問題,這種加載方式在加載執行完之前會阻止 onload 事件的觸發,而現在很多頁面的代碼都在 onload 時還要執行額外的渲染工作等,所以還是會阻塞部分頁面的初始化處理:
<body> this is a test <script type="text/javascript"> ~function() { // function async_load() { var s = document.createElement('script'); s.src = 'http://china-addthis.googlecode.com/svn/trunk/addthis.js'; document.body.appendChild(s); // } // window.addEventListener('load', async_load, false); }(); window.onload = function() { var txt = document.createTextNode(' hello world'); document.body.appendChild(txt); }; </script> <script type="text/javascript" src='http://libs.baidu.com/jquery/2.0.0/jquery.min.js'></script> </body>
比如上面的代碼不能很好地渲染”hello world”,我們只需將注釋去掉就可以了,讓谷歌提供的js在onload 時才開始異步加載。這樣就解決了阻塞 onload 事件觸發的問題。
補充DOMContentLoaded 與 OnLoad 事件 DOMContentLoaded : 頁面(document)已經解析完成,頁面中的dom元素已經可用。但是頁面中引用的圖片、subframe可能還沒有加載完。 OnLoad:頁面的所有資源都加載完畢(包括圖片)。浏覽器的載入進度在這時才停止。這兩個時間點將頁面加載的timeline分成了三個階段。
以上似乎能較好解決這個問題,但是html5提供了更簡便的方法,async屬性!
this is a test <script type="text/javascript" src='http://china-addthis.googlecode.com/svn/trunk/addthis.js' async='async'></script> <script type="text/javascript" src='http://libs.baidu.com/jquery/2.0.0/jquery.min.js'></script> <script type="text/javascript"> console.log('hello world'); </script>
async是html5的新屬性,async 屬性規定一旦腳本可用,則會異步執行(一旦下載完畢就會立刻執行)。
需要注意的是async 屬性僅適用於外部腳本(只有在使用 src 屬性時)
defer屬性常常和async一起提起:
this is a test <script type="text/javascript" src='http://china-addthis.googlecode.com/svn/trunk/addthis.js' defer='defer'></script> <script type="text/javascript" src='http://libs.baidu.com/jquery/2.0.0/jquery.min.js'></script> <script type="text/javascript"> console.log('hello world'); </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的區別):
但是在我看來(以下個人理解,如有出入還望指出),defer在異步加載上的應用並不會比async廣。async的英文解釋是異步,該屬性作用在腳本上,使得腳本加載(下載)完後隨即開始執行,和動態插入script標簽作用類似(async只支持h5,後者能兼容浏覽器);而defer的英文解釋是延遲,作用也和字面解釋類似,延遲腳本的執行,使得dom元素加載完後才開始有序執行腳本,因為有序,所以會帶來另一個問題:
this is a test <script type="text/javascript" src='http://china-addthis.googlecode.com/svn/trunk/addthis.js' defer='defer'></script> <script type="text/javascript" src='http://libs.baidu.com/jquery/2.0.0/jquery.min.js' defer='defer'></script> <script type="text/javascript" src='index.js' defer='defer'></script>
console.log('hello world');
如果執行這段代碼,控制台的“hello world”也會遲遲得不到結果。所以我覺得還是async好用,如果要考慮依賴的話,可以選擇requirejs、seajs等模塊加載器。
JavaScript的異步加載還有一些方式,比如:AJAX eval(使用AJAX得到腳本內容,然後通過eval(xmlhttp.responseText)來運行腳本)、iframe方式等。
以上理解如果有出入,還望指出~
高性能JavaScript編程(高清PDF原版)及中英文對照版 PDF http://www.linuxidc.com/Linux/2015-08/121418.htm