网页的渲染方式概览
-
render树的绘制
-
DOM树的绘制
-
深度优先原则
-
异步加载方式 : 比如
<img src='source'/>
, 元素节点的解析和图片资源的下载是异步的
-
-
css树的绘制
- 深度优先原则
-
-
js渲染引擎按照render树的规则绘制页面
-
dom操作会导致dom树的重排( reflow )或css树的部分重绘( repaint )
-
reflow浪费效率较多, repaint浪费效率较少
-
异步加载JS
-
由于js可以操作dom, 故js代码的执行会阻断页面的渲染
-
但当加载过多js时一旦网速不好, 整个网站将等待js加载而不进行后续渲染等工作( 比如过长白屏时间 )
-
有些js我们还希望用到才加载( 按需加载 )
-
况且在加载一些工具js时也没必要阻塞文档
-
-
所以产生了异步加载js的需求
-
异步加载js的三种方案
- 只有IE能用 : 使用defer属性让script标签异步加载js代码, 其中加载的js代码要等到dom文档全部解析完才会被执行( 不阻塞页面 ), 可将代码写到script标签内部
// 方式一 <script src="tools.js" defer="defer"></script> // 方式二 <script defer="defer"> var demo = 123; </script>
-
W3C标准方法 : 使用async属性让script标签异步加载js代码, 其中加载的js代码加载完就会被执行( 不阻塞页面 ), 不能将代码写到script标签内部
-
常用兼容性好且实现了按需加载的方法 : , 加载完毕后callback
// 创建script标签 var script = document.createElement('script'); script.type = "text/javascript"; // 插入到dom中, 使js可被执行 document.head.appendChild(script); // 绑定监听tools.js下载进度的事件 if (script.readyState) { script.onreadystatechange = function () { if (script.readyState === 'complete' || script.readyState === 'loaded') { // IE版本 : tools.js下载完毕 } } } else { script.onload = function () { // 非IE : tools.js下载完毕 } } // 开始异步下载tools.js script.src = "tools.js";
JS加载的时间线
-
创建Document对象, 开始解析web页面,
document.readyState = 'loading'
-
遇到link外部css, 创建新的线程加载, 并继续解析文档
-
遇到script外部js, 并且没有设置async/defer, 浏览器加载js并阻塞页面, 等待js加载完成并执行该脚本, 然后继续解析文档
-
遇到script外部js, 并且设置async/defer, 创建新的进程异步加载js, 主线程继续解析文档
-
注意script标签也算作DOM节点
-
注意异步加载的js禁止使用document.write(), 因为该方法会清空之前的文档流, 示例如下
// html <div style="width:100px;height:100px;background-color:red"></div> // js window.onload = function () { document.write('a'); }
-
-
遇到img等, 先正常解析dom结构, 然后浏览器开启新线程异步加载src, 并继续解析文档
-
当文档解析完成后( 生成DOM树 ),
document.readyState = 'interactive'
; -
document.readyState === ‘interactive’, 所有设置有defer的脚本会按照顺序执行
-
document对象触发DOMContentLoaded事件( 只能用addEventListener监听该事件 ), 这也标志着程序执行从同步脚本执行阶段转化为事件驱动阶段
-
当所有的async的脚本加载完并执行后, img等加载完后,
document.readyState = 'complete'
, window对象触发load事件 -
从此, 以异步响应方式处理用户输入, 网络事件等
// 时间线三部曲验证
<script>
console.log(document.readyState);
addEventListener('DOMContentLoaded', function(){
console.log(document.readyState);
}, false);
document.onreadystatechange = function () {
console.log(document.readyState);
}
</script>
-
jQuery保证页面解析完毕之后再操作dom的写法如下
- 原理就是基于document.readyState === 'interactive’状态和DOMContentLoaded事件
// jQuery.js $(document).ready(function(){ ... });