JavaScript在瀏覽區中的性能,能夠認爲是開發者所面臨的最嚴重的可用性問題。 優化這個問題的第一步從它的加載和執行開始。javascript
霸道的script標籤
script標籤每次出現都會霸道地讓頁面等待腳本的解析和執行,不管當前的JavaScript代碼是內嵌仍是外聯,頁面的下載和渲染都必須停下來等待腳本執行完畢。這是頁面生存週期的必要環節,由於腳本執行過程當中可能會修改頁面的內容。比較典型的是document.write()與innerHTML,在JavaScript腳本解析並執行這個過程當中,頁面的渲染和用戶交互是徹底阻塞的。java
腳本位置
通常狀況下,我是習慣用外聯方式引入js文件,而且一般將他們放在head標籤上,可是如今我必需要改變這種習慣了。
這種看似正常的代碼組織實際上有很是嚴重的性能問題:加入我在head標籤內加載了兩個JavaScript文件,那個人頁面的渲染將等到這兩個文件加載和執行完畢纔開始,由於瀏覽器在解析body標籤以前,不會渲染任何東西。在它們的加載過程當中,將會致使明顯的延遲,一般的表現形式爲:空白頁面,內容沒法瀏覽,沒法交互。
之前的瀏覽器加載腳本是一個加載而且執行完成以後纔會去加載下一個,如今這個問題有了改善,即如今容許並行下載JavaScript腳本了,可是上述問題仍然成立。因此這裏我學習一下雅虎特別性能小組提出的優化JavaScript的首要原則:將腳本放在底部!瀏覽器
組織腳本
1001 > 254
由於每一個script標籤初始下載都會阻塞頁面渲染,因此咱們須要減小script標籤的數量來限制HTTP請求數從而改善性能,最好將多個JavaScript文件合併成一個。緩存
無阻塞腳本
無阻塞腳本的祕訣在於:在頁面加載完以後才加載JavaScript代碼。這意味着在window對象的load事件觸發後再下載腳本。這樣作的好處是當你下載一個較大的JS文件時,瀏覽器仍是會鎖死一段時間,爲了不這種狀況,咱們須要向頁面逐步加載JavaScript文件。安全
延遲的腳本app
script定義了一個擴展屬性:defer。defer屬性指明本元素所含的腳本不會修改DOM,所以代碼可以安全地執行,可是瀏覽器的支持狀況不理想。
除此以外,HTML5引入了async屬性,用於異步加載腳本,asnyc與defer相同點在於都用於異步加載腳本,採用的是並行下載,在下載過程當中不會產生阻塞。區別在於,asnyc是在加載完成後自動執行,而defer須要等待加載頁面完成後執行,在onload事件處理器執行以前被調用。異步
動態腳本元素async
用於DOM的存在,咱們能夠用JavaScript動態建立HTML裏面的元素,固然也包括script元素,這是動態腳本元素的大前提。
這項技術的重點在於:我經過在頁面內動態建立一個script元素,而且經過給其src屬性賦值來加載腳本,那麼腳本會在頁面內script元素被建立後纔開始加載,那麼不管在什麼時候啓動下載,文件的下載和執行過程不會阻塞頁面的其餘進程。函數
新建立的script元素放在哪?
一般來說,把新建立的script標籤添加到head比添加到body中更加保險,由於當body中的內容沒有所有加載完成時,IE可能會拋出一個「操做已終止」的錯誤信息。性能
動態建立的文件在被下載後怎樣執行?
對於Firefox,Opera,Chrome,Safari來講,會在<script>元素接收完成時觸發一個load事件;可是IE不一樣,它觸發readystatechange事件。<script>元素提供一個readyState屬性,它的值在外鏈文件的下載過程當中的不一樣階段會發生不一樣的變化,IE這些狀態有些不會所有用到,顯示很混亂,最有用的就是"loaded"和"complete"兩種狀態;
"uninitialized"--初始狀態
"loading"--開始下載
"loaded"--下載完成
"interactive"--數據完成下載但尚不可用
"complete"--全部數據已經準備就緒
動態腳本加載憑藉它在跨瀏覽器兼容性和易用的優點,成爲最通用無阻塞加載解決方案;
function loadScript(url,callback){ var script = document.createElement("script"); script.type = "text/javascript"; //IE if(script.readyState){ script.onreadystatechange = function(){ if(script.readyState == "loaded"||script.readyState == "complete"){ script.onreadystatechange == null; callback();//回調 } } }; //其餘瀏覽器 script.onload = function(){ callback(); }; script.src = "file.js";//可用URL取代 //HTML5中用document.head就行了 document.getElementByTagName("head")[0].appendChild(script); }
XHMHttpRequest腳本注入
這裏是另外一種無阻塞腳本模式,先建立一個XHR對象,而後用它下載JavaScript文件,最後經過動態建立的<script>元素將代碼注入頁面中;
var xhr = new XMLHttpRequest(); xhr.open("open","file.js",true); xhr.onreadystatechange = funtion(){ if(xhr.readyState == 4){ 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); } } }
首先發送一個get請求獲取file.js文件,事件處理函數onReadyStateChange檢查readyState是否爲4,同時校準HTTP狀態碼是否爲有效(2XX表示有效響應,304意味着從緩存讀取);
優點:
因爲代碼是在script標籤以外返回的,所以腳本下載完成後不會自動執行,這使得你能夠把腳本的執行推遲到你準備好了以後進行;
各個瀏覽器都支持;
缺點:
JavaScript文件必須與所請求的頁面處於相同的域,這意味着JavaScript文件不能從CDN下載;
總結
向頁面中添加大量javascript的推薦作法只須要兩步:
先添加動態加載所須要的代碼,這段代碼儘可能精簡,甚至能夠只包含loadScript()函數,
第二步就是經過調用函數來加載剩餘的javascript。
對這種推薦作法也分爲兩種方式:
第一種是採用外聯方式加載第一個javascript,並放在</body>以前,這樣作的好處:確保javascript執行過程當中不會阻礙頁面其餘內容的顯示,其次,當剩下的javascript文件完成下載時,應用所需的全部DOM結構已經建立完畢,並作好了交互準備,從而避免了須要另外一個事件,好比window.onload來檢測頁面是否準備好!
第二種方式很簡單,就是將第一個javascript直接內嵌在頁面,從而避免了多產生一次HTTP請求!
若是將JavaScript比做大海,那麼要享受它,首先得確保它到達的是沙灘而不是懸崖,這體如今代碼存放的位置,其次得讓script大海若要波瀾壯闊,則須要納百川,大海的美一部分在於波浪,無阻塞模式的意義正是於此,經過有節奏地加載腳本提升JavaScript性能!