Javascript 加載性能優化
瀏覽器對javascript的處理主要有2部分:下載和執行
下載在有些瀏覽器中是并行的,有些瀏覽器中是串行的,如IE8、Firefox3、Chrome2都是串行下載的
執行在所有瀏覽器中默認都是阻塞的,當js在執行時不會進行html解析等其它操作
阻塞特性:
javascript有個阻塞特性,當瀏覽器執行javascript代碼時,不能同時做其它任何事情。無論當前javascript代碼是內嵌還 是在外鏈文件中,頁面的下載和渲染都必須停下來等待腳本執行完成。瀏覽器在下載和執行腳本是進出現阻塞的原因在于,腳本可能會改變頁面或 javascript的命名空間,它們對后面頁面內容造成影響。
一、腳本位置
瀏覽器在碰到一個引入外部javascript文件的<script>標簽時會停下所有工作來下載并解析執行它,在這個過程中,頁面渲染和用戶交互完全被阻塞了。例:
<html> <head> <title>無標題文檔</title> <link rel="stylesheet" type="text/css" href="styles.css" /> <script type="text/javascript" src="file1.js"></script> <script type="text/javascript" src="file2.js"></script> <script type="text/javascript" src="file3.js"></script> </body> </head> <body> <p>頁面的內容。。。</p> </body> </html>
由于腳本的阻塞特性,頁面會在3個javascript文件全部下載執行完成后,頁面才會繼續渲染,把腳本放在頁面頂部會導致明顯延遲,通常表現為顯示空白頁,用戶無法瀏覽內容,也無法與頁面交互。
ie8+、firefox 3.5+、safari4+、chrome2+都允許并行下載javascript文件,但是在下載的過程中仍然會阻塞圖片等其它資源的下載。
由于腳本會阻塞頁面其它資源的下載,因此推薦將javasrcipt盡量放到body標簽的底部,以減少對整個頁面下載的影響。
二、組織腳本
由于<script>標簽在下載時會阻塞頁面的渲染,所以減少<script>標簽數量有助于改善這一情況。建議將多個javascript文件合并為一個,這樣可以減少性能的消耗。同時也可以減少請求的數量。
(參考:在服務端合并和壓縮javascript和CSS文件)
三、無阻塞腳本
1、延遲腳本
HTML4 為<script>標簽定義了一個defer 屬性,它能使這段代碼延遲執行,然而該屬性只有IE4+支持,因此它不是一個理想的跨瀏覽器解決方案。聲明了defer 屬性的script會在DOM加載完成,window.onload 事件觸發前被解析執行:
<html> <head> <title>script defer example</title> </body> </head> <body> <script defer> alert('defer'); </script> <script> alert('script'); </script> <script> window.onload = function(){ alert('load'); } </script> </body> </html>
這段代碼在支持defer屬性的瀏覽器彈出順序是:script、defer、load;不支持defer屬性的瀏覽器彈出的順序是defer、script、load。
2、動態腳本元素
<script type="text/javascript"> function loadScript(url, callback) { var script = document.createElement('script') script.type = 'text/javascript'; if (script.readyState) { //for ie script.onreadystatechange = function() { if (script.readyState == 'loaded' || script.readyState == 'complete') { script.onreadystatechange = null; callback(); } }; } else { //other browser script.onload = function() { callback(); }; } script.src = url; document.getElementsByTagName('head')[0].appendChild(script); } </script>
loadscript函數用法
<script type="text/javascript"> //單個文件 loadScript('file1.js', function(){ alert('loaded!'); }); //多個文件 loadScript('file1.js', function(){ loadScript('file2.js',function(){ loadScript('file3.js', function(){ alert('all files loaded!'); }); }); }); </script>
這種技術的重點在于:無論何時啟動下載,文件的下載和執行過程不會阻塞頁面其它進程,你甚至可以將代碼放在頁面的head區域而不影響頁面的其它部分(下載該文件的http鏈接除外)。
3、XMLHttpRequest 腳本注入
此技術會先創建一個XHR對象,然后用它下載javascript文件,最后創建動態的script元素將代碼注入到頁面中。
<script type="text/javascript"> var xhr = new XMLHttpRequest(); xhr.open('get', 'file1.js', true); xhr.onreadystatechange = function() { if (xhr.status >= 200 && xhr.status <300 || xhr.status == 304) { var script = document.createElement('script'); script.type = 'text/javascript'; script.text = xhr.responseText; document.body.appendChild(script); } }; xhr.send(null); </script>
這種方法優點是可以直接下載javascript代碼但不立即執行。由于代碼是在<script>標簽之外返回的,因此下載后不會自動 執行,這使得是可以把腳本推遲到你準備好的時候。這種方法的局限性在于javascript文件必須與所請求的頁面處于相同的域,這意味著 javascript文件不能從cdn下載,因此不適合大型網站或項目。
四、推薦的無阻塞加載方式
1、YUI3的方式
2、LazyLoad(1.5k)
Yahoo!Search工程師Ryan Grove創建的一個通用的延遲加載工具,是loadScript()函數的增強版。
用法示例:
<script type="text/javascript" src="lazyload-min.js"></script> <script type="text/javascript"> LazyLoad.js('the-reset.js', function(){ Application.init(); }); </script>
LazyLoad同樣支持多個javascript文件,并能保證在所有瀏覽器中都可以按正確的順序執行。要加載多個javscript文件,只需要給LazyLoad.js()y方法傳入一個url數組:
<script type="text/javascript" src="lazyload-min.js"></script> <script type="text/javascript"> LazyLoad.js(['first.js', 'the-reset.js'], function(){ Application.init(); }); </script>
項目地址:https://github.com/rgrove/lazyload
3、LABjs(4.7k)
LABjs是Kyle Simpson受Steve Sounders的啟發實現的無阻塞加載工具。用法示例:
<script type="text/javascript" src="lab.js"></script> <script type="text/javascript"> $LAB.script('the-reset.js') .wait(function(){ Application.init(); }); </script>
$LAB.script()方法用來定義需要下載的javascript文件,$LAB.wait()用來指定文件下載并執行完畢后所調用的函數。
要下載多個javscript文件,只需鏈式調用另一個$LAB.script()方法:
<script type="text/javascript" src="lab.js"></script> <script type="text/javascript"> $LAB.script('first.js') .script('the-reset.js') .wait(function(){ Application.init(); }); </script>
LABjs與眾不同的是它管理依賴關系的能力。通常來說,連續的<script>標簽意味著文件逐個下載并按順序執行。
LABjs允許使用wait()方法來指定哪些文件需要等待其它文件。上面的例子中first.js不能保證會在the-reset.js的代碼前執行,為了確保這一點,必須在第一個script()方法后調用wait():
<script type="text/javascript" src="lab.js"></script> <script type="text/javascript"> $LAB.script('first.js').wait() .script('the-reset.js') .wait(function(){ Application.init(); }); </script>
項目地址:hhttp://labjs.com/
4、SeaJS(7.5k)
SeaJS 是淘寶玉伯開發的一個遵循 CommonJS 規范的模塊加載框架,可用來輕松愉悅地加載任意 javascript 模塊。詳細請參考:http://seajs.com/docs/
5、do 框架(3.5k)
Do是豆瓣網kejun開發的一個很輕量的Javascript開發框架。目前do.min.js。它的核心功能是對模塊進行組織和加載。加載采取并行異步隊列的策略,并且可以控制執行時機。Do可以任意置換核心類庫,默認是jQuery。
項目地址:https://github.com/kejun/Do