在打開一個站點的時候,瀏覽器會去加載各類資源。如今對JS的使用很是廣泛,任何一個站點都會請求大量的JS腳本,而加載和執行的方式也是各不相同,但願讀完這篇文章能夠對經常使用的加載和執行方式有一個總體的認識。javascript
首先介紹的是html中直接使用<script>
標籤。這也是咱們最多見的一種加載腳本的方式。html
// 直接使用script內嵌腳本 <script> document.getElementById("demo").innerHTML = "Hello JavaScript!"; </script>// 使用script的src屬性引用外部腳本 <script src="myscripts.js"></script>
這種方式在主流的瀏覽器能夠是並行加載的,可是執行腳本的順序仍是同步的,再加上瀏覽器是順序解析頁面,因此對於腳本的位置是有必定講究的:java
- 若是腳本B須要使用到腳本A中的數據(好比fn或者變量),那腳本B必須放在腳本A後面。
- 由於執行引擎是單線程的,因此在執行JS的時候會阻塞DOM的渲染,致使頁面長時間空白。若是不想的話能夠考慮把JS放在文檔的後面(好比body標籤的底部)。
- 一個外鏈腳本就涉及到一個請求,再小的請求確定都會有性能開銷,好比請求頭,網絡時延等等。因此在優化站點性能的時候減小外鏈也是要考慮的一個點。
【注】:可並行加載的瀏覽器包括:IE8+、firefox3.5+、safari4+和chrome2+。不一樣瀏覽器對於同一個域名下的最大鏈接數有不一樣的限制,基本在6個左右。具體能夠參見 這篇文章。
在程序的世界中,不少場景下同步阻塞就意味着性能問題。在這些場景下其實無阻塞腳本就能夠搞定,主進程仍是去幹當前最主要的事情,即加載和渲染DOM,而無阻塞的腳本能夠等到頁面加載完再去加載執行,這些場景正是性能優化的點。git
這就引伸出來了幾種無阻塞腳本的方案。github
這個屬性的是承諾用src引的腳本中不會修改DOM。放心的讓這個腳本延遲執行吧。具體延遲到文檔完成解析後,觸發 DOMContentLoaded 事件前執行。chrome
【注1】執行是被延遲了,可是下載仍是根據script在頁面中的位置。解析到時會去並行下載,可是不會執行。
【注2】由上述定義能夠看出來需由src的存在,對於內嵌的腳本是無效的。
【注3】配了defer屬性的腳本之間是按照順序執行的【測試】 chrome 64.0.3282.119瀏覽器
<script src="./demo.js" defer></script> <script defer> console.log('script with defer'); </script> <script> console.log('script withour defer'); </script> <script> window.addEventListener("load",function (event) { console.log("All resources finished loading!"); }); document.addEventListener("DOMContentLoaded", function(event) { console.log("DOM fully loaded and parsed"); }); </script> </body>// demo.js console.log('inner demo.js');
結果:
性能優化
配置了async屬性是告訴瀏覽器,這個腳本異步去並行加載,加載完當即異步執行,可是加載的時機是不肯定的,因此這個屬性比defer更開放。相關測試代碼網絡
【注1】由於異步加載完就當即異步執行,因此配了這個屬性的腳本之間的關係也是不肯定的。因此 不能存在依賴async腳本內容的狀況。
【注2】執行的時機智能肯定在load事件以前,和DOMContentLoaded的時機不能肯定
【注3】優先級是高於defer的
【注4】和defer同樣,對內嵌腳本無效;不能有document.write
改寫dom的代碼
動態腳本是咱們比較經常使用的異步加載和執行JS的方式。這種實現要特別注意瀏覽器的兼容性。簡單的實現方式以下:app
function loadJs(url,callback) { var callback = callback || (() => {}); var script = document.createElement('script'); script.type = "text/javascript"; script.src = url; if(script.readyState){ //IE script.onreadystatechange = function () { if(script.readyState == "loaded" || script.readyState == 'complete'){ console.log('inner onreadystatechange'); script.onreadystatechange = null; callback(); } }; } else { script.onload = function () { console.log('inner onload'); callback(); }; } document.getElementsByTagName("head")[0].appendChild(script); // 開始下載並執行 } loadJs("./server.js");
這種建立的方式,文件在該元素被添加到頁面時開始下載,加載完開始執行,而且文件的下載和執行過程不會阻塞其它進程。能夠認爲這種建立默認加了async屬性。咱們還能夠經過設置async = false
的方式取消異步的特性。正由於這個特性,絕大多數場景下都是有益的,可是當咱們想使用這種方式去加載多個JS時,而且有前後順序的時候,能夠嘗試在callbak裏去迭代發請求。
loadJs('f1.js',()=>{ loadJs('f2.js',()=>{ loadJs(xxx); }) });
搞過Ajax的對XHR應該都很熟悉了,在這就不詳細介紹了,須要的去Google一把。
XHR主要是請求腳本,而後咱們能夠控制請求回來的腳本,在咱們須要的時候經過上述動態建立腳本的方式注入到頁面中。這種方式最大的好處就是兼容性好。弊端也很明顯,必須同源。下面是一個簡單實現,沒有考慮在建立xhr的兼容性,好比ActiveXObject,有須要的能夠去google一把:
var xhr = new XMLHttpRequest(); // 建立xhr對象 xhr.open("get",'./server.js',true); // 初始化一個請求, 支持CRUD xhr.onreadystatechange = function () { if(xhr.readyState == 4){ if(xhr.status >= 200 && xhr.status < 3000 || xhr.status == 304){ var script = document.createElement('script'); script.type = 'text/javascript'; script.text = xhr.responseText; document.body.appendChild(script); } } } xhr.send(null); // 發送請求
這地方說明一下xhr.status:
值 | 狀態 | 描述 |
---|---|---|
0 | UNSENT (未打開) | open()方法還未被調用. |
1 | OPENED (未發送) | open()方法已經被調用. |
2 | HEADERS_RECEIVED (已獲取響應頭) | send()方法已經被調用, 響應頭和響應狀態已經返回 |
3 | LOADING (正在下載響應體) | 響應體下載中; responseText中已經獲取了部分數據. |
4 | DONE (請求完成) | 整個請求過程已經完畢. |
document.write
對於document.write
,通常都不推薦使用的,主要是由於存在write
方法的腳本可能會在解析的過程當中修改DOM,致使一些腳本沒法預加載,甚至會致使一些已經預解析和預加載失效。網上也不少關於爲何要避免使用document.write
的文章,感興趣的能夠去google一把。
innerHtml
對於innerHtml
和outerHTML
, 只會以字符串的形式來承載,不會去執行對應的腳本的。
總結:上文主要介紹了動態建立腳本和XHR的方式去建立異步加載和執行腳本的方式。在某些性能調優的狀況下仍是頗有用的,而XHR更是Ajax的核心。
高性能Javascript