浏览器渲染原理图:
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解析完成了,但是渲染被阻塞着。
来源:https://www.cnblogs.com/bibiafa/p/9364986.html