瀏覽器渲染原理圖:
bar.js
var count_bar = 0; var start_bar = new Date(); for(var i=0;i<100000;i++){ for(var j=0;j<10000;j++){ count_bar++; } } var end_bar = new Date(); console.log(end_bar - start_bar,'bar');
foo.js
var count_foo = 0; var start_foo = new Date(); for(var i=0;i<100000;i++){ for(var j=0;j<10000;j++){ count_foo++; } } var end_foo = new Date(); console.log(end_foo - start_foo,'foo');
ress.js
var count_ress = 0; var start_ress = new Date(); for(var i=0;i<100000;i++){ for(var j=0;j<10000;j++){ count_ress++; } } var end_ress = new Date(); console.log(end_ress - start_ress,'ress');
demo.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> <script src="js/bar.js"></script> <script src="js/foo.js"></script> <script src="js/ress.js"></script> </head> <body> <div id="dd"> div 1 </div> <p>paragraph</p> <div> div 2 </div> </body> </html>
來自於safari的截圖
1.現代瀏覽器會並行加載js文件,參見截圖的start time列,但是按照書寫順序執行代碼
2.加載或者執行js時會阻塞對標簽的解析,也就是阻塞了dom樹的形成,只有等到js執行完畢,瀏覽器才會繼續解析標簽。沒有dom樹,瀏覽器就無法渲染,所以當加載很大的js文件時,可以看到頁面很長時間是一片空白
之所以會阻塞對標簽的解析是因為加載的js中可能會創建,刪除節點等,這些操作會對dom樹產生影響,如果不阻塞,等瀏覽器解析完標簽生成dom樹后,js修改了某些節點,那么瀏覽器又得重新解析,然后生成dom樹,性能比較差
修改html,添加事件監聽
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> <script src="js/bar.js"></script> <script src="js/foo.js"></script> <script src="js/ress.js"></script> </head> <body> <div id="dd"> div 1 </div> <p>paragraph</p> <div> div 2 </div> <img src="images/beauty.png" alt="" onload="console.log('image loaded')"> <script> document.addEventListener("DOMContentLoaded",function(){ console.log("dom content loaded"); }) window.onload = function(){ console.log('resources loaded'); } </script> </body> </html>
來自chrome的截圖
1.文檔解析完成時觸發domcontentloaded事件。瀏覽器逐行解析,遇到</html>表示解析完成
2.當所有的資源都加載完后觸發window的load事件。
3.監聽資源加載完成有四種方式
3.1 window.onload = function(){....}
3.2 window.addEventListener("load",function(){....});
3.3 document.body.onload = function(){....}
3.4 <body onload = "....">
錯誤方式: document.body.addEventListener('load',function(){....});
這塊各瀏覽器表現沒有統一標准,推薦使用window.onload來監聽,比較保險
給script標簽添加defer屬性,僅限外部腳本
<script src="js/bar.js" defer></script>
結果:
1.defer屬性表示延遲腳本的執行,等到整個文檔解析完再執行
2.defer屬性能延遲執行,但是不會延遲下載,瀏覽器遇到script就立即下載腳本
3.文檔解析完成時,腳本被執行,此時也會觸發domcontentloaded事件,優先執行腳本
4.多個標簽添加defer屬性,執行順序仍然是按書寫順序
給script標簽添加async屬性,僅限外部腳本
<script src="js/bar.js" async></script> <script src="js/foo.js" async></script> <script src="js/ress.js" async></script>
結果
chrome:
safari:
firefox:
1.async屬性的作用是讓瀏覽器異步加載腳本文件。在加載腳本文件的時候,瀏覽器能繼續標簽的解析。
2.異步腳本一定會在load事件之前執行,但可能會在domcontentloaded事件之前或者之后執行。
3.異步腳本之間的執行順序不確定,可能不會按照書寫順序執行
刪除script外部鏈接
demo.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> </head> <body> <div> before </div> <script> var count = 0; for (var i = 0; i < 100000; i++) { for (var j = 0; j < 10000; j++) { count++; } } console.log(count); </script> <div id="dd"> div 1 </div> <p>paragraph</p> <div> div 2 </div> <img src="images/beauty.png" alt="" onload="console.log('image loaded')"> <script> document.addEventListener("DOMContentLoaded", function () { console.log("dom content loaded"); }) window.onload = function () { console.log('resources loaded'); } </script> </body> </html>
結果是:
1.持續一段空白頁面后,才有東西出來。也就是說頁面元素的渲染是整體的。瀏覽器構建完整個DOM樹后渲染,不會因為<div>before</div>出現在<script>....</script>之前,before就會先出現。
2.通常把script內容放在body最后,這樣腳本文件不會阻止其他資源的下載,但是由於DOM的解析完成是依據是否遇到</html>標簽,那么瀏覽器當遇到script標簽時會立即執行里面的代碼,導致DOM的解析被阻塞。
css對渲染的阻塞
<html lang="en"> <head> <title>css阻塞</title> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <script> console.log('before css'); document.addEventListener("DOMContentLoaded",function(){ console.log("content loaded"); f(); }) function f(){ console.log(document.querySelectorAll("h1")); } </script> <link href="https://cdn.bootcss.com/bootstrap/4.0.0-alpha.6/css/bootstrap.css" rel="stylesheet"> <link href="http://netdna.bootstrapcdn.com/font-awesome/4.4.0/css/font-awesome.css" rel="stylesheet"> <link href="http://apps.bdimg.com/libs/bootstrap/2.3.2/css/bootstrap-responsive.css" rel="stylesheet"> <script> console.log("after css"); </script> </head> <body> <h1>這是紅色的</h1> <h1>head</h1> </body> </html>
safari截圖:
輸出結果:
1.css文件是並行下載的
2.css的下載會阻塞后面js的執行,以上代碼先執行"before css",然后開始下載三個css文件,文件下載完成,執行"after css"
3.css的下載不會阻塞后面js的下載,但是js下載完成后,被阻塞執行
將"after css"那段代碼注釋掉,發生了巨大的改變
可以看到domcontentloaded事件觸發了,但是此時頁面是空白的
這是因為雖然js會阻止dom的解析,但是css不會阻止dom的解析。
第一個案例中,遇到"before css"代碼,dom被阻塞,執行js代碼,執行完之后繼續解析,遇到link標簽后,開始下載css文件,dom解析繼續,遇到script標簽,dom解析被阻塞,且js代碼不會被執行,等到css文件下載並且解析完成后,js代碼開始執行,執行完之后,繼續dom解析,最后生成dom樹,拋出domcontentloaded事件,然后瀏覽器開始渲染頁面,出現頁面元素。
第二個案例中,沒有"after css"代碼,那么在下載css文件的時候,dom解析沒有被阻塞,那么當設置下載網速20kb時,css還沒下載解析完成,dom解析就已經完成了。此時拋出domcontentloaded事件,但是瀏覽器此時還不能渲染元素,因為元素的渲染除了dom樹外還需要cssdom配合,確定元素的大小位置。這樣就導致了css文件沒有下載解析完成時,dom解析完成了,但是渲染被阻塞着。