瀏覽器的多線程中,有的線程負責加載資源,有的線程負責執行腳本,有的線程負責渲染界面,有的線程負責輪詢、監聽用戶事件。
這些線程,根據瀏覽器自身特色以及web標準等等,有的會被瀏覽器特地的阻塞。兩個很明顯的阻塞就是:腳本執行時對其餘線程的阻塞和腳本加載時對其餘線程的阻塞。
這兩個阻塞發生在HTML頁面初次解析時,它們對性能的影響較大,緣由是:
document對象綁定了一個事件:DOMContentLoaded。這個事件會在DOM解析完成以後觸發。這個事件觸發以後(而不是window.load事件),會進入異步事件驅動階段(另外一個線程控制)。也就是說,DOM解析工做不完成,用戶與頁面的不少(並非全部)事件交互就沒法進行。這時候瀏覽器的忙指示(那個頁面上方的煩人的旋轉的圓圈)不會消失。
DOMContentLoaded何時觸發?
DOMContentLoaded事件自己不會等待CSS文件、圖片、iframe加載完成。
DOMContentLoaded的觸發時機是:加載完頁面,解析完全部標籤(不包括執行CSS和JS),可是JS的執行,須要等待位於它前面的CSS加載(若是是外聯的話)、執行完成,由於JS可能會依賴位於它前面的CSS計算出來的樣式。因此:
注:現代瀏覽器會併發的預加載CSS、JS、IMG(例如:當 HTML 解析器(HTML Parser)被腳本阻塞時,解析器雖然會中止構建 DOM,但仍會識別該腳本後面的資源,並進行預加載)。可是,執行CSS和JS的順序仍是按原來的依賴順序(JS的執行要等待位於其前面的CSS和JS加載、執行完)——先加載完成的資源,若是其依賴還沒加載、執行完,就只能等着。
因此就形成外部資源阻塞渲染,如CSS 與 JavaScript
默認狀況下,CSS 被視爲阻塞渲染的資源,存在阻塞的 CSS 資源時,瀏覽器會延遲 JavaScript 的執行和 DOM 構建,這意味着瀏覽器將不會渲染任何已處理的內容,直至 CSSOM 構建完畢。
-
css加載不會阻塞DOM樹的解析
-
css加載會阻塞DOM樹的渲染
-
css加載會阻塞後面js語句的執行
css會阻塞js,同理,css也會阻塞img解碼、paint(瀏覽器認爲你的CSS沒有加載完畢,不肯定圖片的樣式到底如何,牽扯到重繪資源問題),js不會阻塞img的解碼、paint(估計chrome作了優化,具體本人還不知,但願客官補充)。
css阻塞優化:
由於渲染線程和js線程與資源進行加載的線程並不互斥,不會互斥意味着:資源的加載能夠和UI渲染、重排,事件響應,或者JavaScript代碼的執行的併發進行。
這裏還有一個知識點:下載的最大並行數指的是從一個主機上下載的最大並行數,若是從多個主機下載資源,這個數量會翻倍,可是因爲對DNS的解析也是一個性能優化的點,故而通常策略是:不該設置超過4個主機,最好只設置2個主機。
可是操蛋的就是,若是瀏覽器解析DOM時須要下載腳本資源,那麼下載這個資源的線程就是阻塞其餘下載線程以及渲染線程,致使渲染速度變慢。
可是假設該腳本下載的速度較慢,並且多個腳本非併發下載,而且假如多個<script>內腳本執行時間較長的話,DOM解析工做仍是會一直完不成。
js阻塞優化
由於:腳本執行和渲染DOM的併發可能會引起嚴重的衝突(腳本能夠修改DOM)
因此:JavaScript引擎和渲染引擎所在的兩個線程被設計爲互斥的!
這就意味着:在執行<script>中內容時,瀏覽器會切換到JavaScript引擎所在的線程,此時渲染引擎所在的線程會阻塞,故其後元素的解析和渲染會暫停。這時候若是腳本執行時間太長的話,不只後面的元素會一直看不到,對DOM的解析工做也會一直完不成。用戶會陷入焦急的等待中。
一、把<script>放到緊跟</body>以前的位置
這樣就不會影響須要放到頁面上的UI元素的解析了。這樣的好處就是,用戶能即便看到頁面上的UI元素,而防止出現了瀏覽器白屏等現象。
由於document.createElement("script")的async屬性默認爲true,而document.head.appendChild代碼以後,因爲沒有觸發渲染樹的重繪,切換回的渲染線程會將剩下的DOM解析並渲染完畢。同時新插入的<script>中的資源也會併發的下載。
var script=document.createElement("script"); console.log(script.async);//true
同理:用XHR對象下載代碼,並注入到頁面也能夠達到一樣的效果
若是須要同步執行,須要將async屬性設置爲fasle
三、h5時代,script添加defer或asyn兩個屬性(html4.0中定義了defer;html5.0中定義了async)
-
若是 script 標籤中包含 defer,那麼這一塊腳本將不會影響 HTML 文檔的解析,而是等到 HTML 解析完成後纔會執行。而 DOMContentLoaded 只有在 defer 腳本執行結束後纔會被觸發。即:整個 document 解析完畢且 defer-script 也加載完成以後(這兩件事情的順序無關),會執行全部由 defer-script 加載的 JavaScript 代碼,而後觸發 DOMContentLoaded 事件。defer不會改變script中代碼執行順序
-
若是 script 標籤中包含 async,則 HTML 文檔構建不受影響,不須要等待 async-script 執行。可是,async-script 加載完成後,就會當即執行!若是頁面仍是沒有解析完成,就會停下來(阻塞頁面)等此腳本執行完畢再繼續解析。async-script 可能在 DOMContentLoaded 觸發以前或以後執行,但必定在 load 觸發以前執行。並且:多個 async-script 的執行順序是不肯定的。
document.readyState
說道DOMContentLoaded,不得不提readystatechange,經過document.readyState值來更進一步來判斷文檔狀態:
-
uninitiated:xml 對象被產生,但沒有任何文件被加載。
-
loading:document正在下載,文件還沒有開始解析。
-
loaded:部分的文件已經加載且進行解析,但對象模型還沒有生效。
-
interactive:document完成了解析,可是資源還在下載,對象模型是有效但只讀的。
-
complete:表明加載成功,文檔加載完成,而且全部resource都加載完畢
經過下面代碼驗證,在chrome上貌似只有 interactive和complete。
document.addEventListener("DOMContentLoaded",function () { console.log("DOMContentLoaded"+new Date()) }); document.addEventListener("readystatechange",function () { console.log("B_____"+new Date()); console.log(document.readyState) // switch (document.readyState){ // case "loading": // console.log("LOADING"+new Date()); // break; // case "loaded": // console.log("loaded"+new Date()); // break; // case "interactive": // console.log("interactive"+new Date()); // break; // case "complete": // console.log("complete"+new Date()); // break; // } }); console.time("A")
B_____Thu May 17 2018 10:23:36 GMT+0800 (CST)
DOMContentLoadedThu May 17 2018 10:23:36 GMT+0800 (CST)
B_____Thu May 17 2018 10:23:36 GMT+0800 (CST)
這裏又有疑問:interactive DOMContentLoaded complete onload三個前後順序是什麼呢?
DOMContentLoaded和interactive:表示文檔解析完成,且資源未徹底加載完成。區別呢?執行順序呢?
驗證代表:interactive 》DOMContentLoaded 》 complete 》 onload
可是,DOMContentLoaded觸發時候,document.readyState通常是interactive,也有可能complete。而當頁面有大量的二進制文件(頁面加載的時長大於阻塞的時長的時候),document.readyState=complete 可能反而在 onload 事件以後才能觸發(這個我未完成驗證出這種狀況)
我以爲onreadystatechange這個不是很靠譜,通常用DOMContentLoaded判斷頁面解析徹底。但願哪位大牛提供這方面的補充,感激涕零!
document.getElementById('load').onclick = function() { var img = new Image(); if(img.complete) { console.log('dd'); } img.onload = function() { console.log('ff') } img.src="images/1-logo.png"; }
-
onload:表示加載好,換言之,沒有加載好不會執行;
-
onAbort:圖片加載的時候,用戶經過點擊中止加載時出發
-
onerror:若是圖片不存在(網絡很不通暢,也可能觸發 onerror事件)
-
complete:圖片顯示出來之後爲true,