时间线、解析与渲染
1. 从document.write说起
使用 document.write 方法向文档写入元素,默认不会覆盖body中存在的元素,正常追加,然而在 window.onload 的事件处理函数中执行,会替换掉body中已存在的元素内容。为什么会发生这样的现象,产生这样现象的原理是什么?
2. 时间线
浏览器从页面加载开始那一刻,到整个页面加载完毕,整个过程中按顺序发生的总流程,就叫做时间线。
时间线大概分为以下几个过程(序号不代表执行顺序,有些是同时发生的)。
1. 生成document对象(#document)
从生成document对象开始,JS开始起作用,DOM的功能体开始起作用。
2. 解析文档,构建DOM树、CSS树
document.readyState = ‘loading’; // 加载中
a. 从html代码的第一行开始,浏览器从第一行阅读到最后一行。
就传统浏览器来说,这个过程实际上是不做任何事情的,只是在阅读。
解析文档的同时,浏览器构建DOM树。
b. 遇到link标签,开新的线程异步加载CSS外部文件,阅读style标签,生成CSSOM(CSS树)。
DOM树和CSS树是同时构建的。
c. 没有设置异步加载的SCRIPT,阻塞文档解析。
只要遇到没有设置异步(async、defer)的script标签或动态设置的scrpt标签,会阻
塞文档解析。DOM和CSS树构建也会停止,等待JS脚本加载并且执行完毕,继续解析文档。
d. 设置异步加载的SCRIPT,异步加载JS脚本并且执行(async),不阻塞解析文档。
异步脚本中,不能直接使用document.write(),使用则会报错。
如果必须使用,可以写在window.onload的事件处理函数中。
异步script不能有依赖其他脚本的操作,不能有需要触发的操作(比如监听文档解析完
成),可以执行网络检查、网络请求等操作。异步操作实际在项目中使用较少。
e. 解析文档时,遇到img标签,先解析节点。遇到src,创建加载线程,异步加载图片资源,不阻塞解析文档。
3. 文档解析完成
document.readyState = ‘interactive’; // 可以进行交互
a. 设置异步(defer)的script,JS脚本开始按照顺序执行。
设置async的标签,异步加载完毕并且直接执行。
设置defer的标签,异步加载完毕,等待文档解析完毕后,开始执行脚本。
b. 触发DOMContentLoaded事件,代表文档解析完成。
文档解析完成,不代表文档加载完毕,如果存在图片,图片可能还在异步加载。
解析完成,代表DOM结构已经生成,渲染树已经生成。
程序从同步的脚本执行阶段向事件驱动阶段演化,用户交互在文档解析完成之后。
4. 文档解析完成、异步资源加载完毕
document.readyState = ‘complete’; // 文档加载完毕
async script加载并执行完毕,img资源加载完毕,window.onload事件被触发。
async的script标签,可能在文档解析完成后才开始加载。
async的和defer不一定谁先加载,都是异步执行。
所有的异步加载完毕,window.onload才被触发,之前一直在阻塞中。
3. 页面加载的三个阶段
1. 解析文档,构建DOM树开始。
document.readyState = ‘loading’; // 加载中
2. 文档解析完成
document.readyState = ‘interactive’; // 解析完成
3. window.onload触发,文档加载完成及资源加载完毕
document.readyState = ‘complete’; // 文档加载完成
文档状态改变触发该事件,监听的过程是由JS引擎完成的,不是用户,不能算是事件驱
动阶段中一种。
4. window.onload 和 DOMContentLoaded 区别?
DOMContentLoaded是文档解析完成后触发。
window.onload是文档解析完成,并且异步资源加载完成后触发,浪费时间。
5. 现代浏览器的布局和渲染
现代浏览器为了更好的用户体验,渲染引擎尝试尽快的的渲染到屏幕上,
先解析的部分先构建CSS树、DOM树和渲染树,即读到哪一部分,就开始渲染哪一部分。
现代浏览器是在解析的过程中就在执行渲染,一边解析一边渲染。
所以script标签应该放在底部,否则会浪费解析script文档的时间。
first paint(初次绘制),只要解析到html中需要渲染的东西,一边解析一边构建一边渲染。
如果把script放在顶部,初次渲染的时间就被延迟,所以可能存在页面留白的情况。不利于用户体验。
DOMContentLoaded:当renderTree全部渲染完毕之后,触发此事件。
6. 封装文档解析完毕函数
**
* 判断文档解析完毕
*
* @param {*} fn
*/
function domReady (fn) {
if (document.addEventListener) {
document.addEventListener('DOMContentLoaded', function () {
document.removeEventListener('DOMContentLoaded', arguments.callee, false);
fn();
}, false);
} else if (document.attachEvent) {
document.attachEvent('onreadystatechange', function () {
if (this.readyState === 'complete') {
document.detachEvent('onreadystatechange', arguments.callee);
fn();
}
})
}
// 判断不在iframe中、兼容IE67
if (document.documentElement.doScroll &&
typeof(window.frameElement) === 'undefined') {
try {
document.documentElement.doScroll('left');
} catch (e) {
return setTimeout(arguments.callee, 20);
}
fn();
}
}