1.引子
最近看到有道题讨论document.write
在html文件中不同位置时在页面上的执行顺序(题目在后面具体讨论),于是写这篇博客讨论一下浏览器的页面渲染,在http请求中传输的字节码如何变成浏览器呈现在用户面前的界面。
2.进程与线程
这似乎是个永恒的话题,很多人开始都是混淆的。对于这两个概念,可以记住下面这几条:
- 进程是cpu资源分配的最小单位(系统会给它分配内存)
- 进程之间相互独立,对于浏览器,每打开一个Tab页都可以认为开了一个新的进程。
- 进程拥有自己的多线程,各个线程之间相互协作完成任务。
在这里我们只讨论一个页面的渲染机制,即下面说的线程都在一个进程内。对于浏览器,开一个新页面(进程)工作的是以下线程:
与浏览器页面渲染有关的主要是和这里的GUI引擎线程和JS引擎线程:
GUI引擎线程(后面我们说 UI线程):解析html css,进行DOM
, CSSOM
, RenderTree
的绘制,回流,重绘的执行者,以及页面渲染都是由他来完成(难以避免的抛出一大堆概念,后面会一一解释)。 - JS引擎线程:用来对js文件进行处理。
- 上面两个线程是互斥的(请记住这句话,很重要),当有一个在进行时,另外一个将被挂起,也就是说会造成阻塞,至于谁阻塞谁,后面再说。
3.当浏览器接收到服务器发过来的数据包时...
- 将数据包进行解析。
- 解析html文件,UI线程进行DOM树的构建,此操作将确定节点的父子以及兄弟关系。
- 当继续解析到类似的
<link rel='stylesheet' href='../example.css'/>
语句时将下载相应的css文件,并进行CSSOM
的构建(类似DOM
的东西),将确定css属性之间的级联关系。 - 浏览器将
DOM
与CSSOM
进行合并,构建RenderTree
,所谓的渲染树。然后浏览器会根据渲染树进行名为reflow
(回流)的过程,来根据浏览器页面的具体情况确定各个节点的渲染位置(该操作会遍历整个DOM
和CSSOM
,对性能影响很大)。 - 接下来就是将准备好的东西渲染到屏幕上了。以上过程可以用下面这张图演示
上面的看似很顺畅的过程,却隐去了一个重大的问题(js呢)?
接上面谈起,浏览器开始解析时碰到
<link rel='stylesheet' href='../example.css'/>
会开始下载css文件,同样当遇到语句时<script src='./example.js'></script>
js引擎会下载并执行js文件,注意这里会引起阻塞,阻塞具体情况如下:- 阻断DOM的构建,应为浏览器不知道js文件会对DOM进行什么操作,也就是说JS执行会阻塞
DOM
构建。 - 如果
CSSOM
没有就绪,那么JS将等到CSSOM
准备就绪时再执行,也就是说CSSOM
的构建会阻塞JS执行,其实也就是间接的在阻塞DOM
的构建。
- 阻断DOM的构建,应为浏览器不知道js文件会对DOM进行什么操作,也就是说JS执行会阻塞
- 那么我们是否能对JS的执行进行操作呢,答案是:可以.
- async属性
<script src='./example.js' async></script>
- 它的作用是指定相应的js文件在下载好再进行执行,也就是说这个js文件在下载过程中是不阻塞
DOM
构建的。
- 它的作用是指定相应的js文件在下载好再进行执行,也就是说这个js文件在下载过程中是不阻塞
- defer属性
<script src='./example.js' defer></script>
- 指定对应js文件在整个页面都解析完成后再执行,此时
DOM
和CSSOM
都已经准备就绪。
- 指定对应js文件在整个页面都解析完成后再执行,此时
- async属性
说一组概念(很重要):
- 回流(reflow)和重绘(repaint)
- 回流:当
Render Tree
中部分或全部元素的尺寸、结构、或某些属性发生改变时,浏览器重新渲染部分或全部文档。 - 重绘:当页面中元素样式的改变并不影响它在文档流中的位置时,浏览器会将新样式赋予给元素并重新绘制它。
- 至于具体什么操作会引起回流以及重绘,这里不一一列出。
- 回流必定重绘,重绘不一定回流。
- 很明显:回流的代价要比重绘高很多。在性能优化时有一点就是避免频繁造成回流。
- 回流:当
4.那么回到我们开篇的问题
先来介绍这条语句:
document.write
document.open()
打开的一个文档流。 注意: 因为 document.write
需要向 文档流中写入内容,因此在关闭(已加载)的文档上调用 document.write
会自动调用 document.open
, 。 Document 页面内容
页面内容
复制代码
页面输出结果:
//脚本输出//页面内容//页面内容复制代码
Document 页面内容
页面内容
复制代码
页面输出结果:
//页面内容//脚本输出//页面内容复制代码
以上的结果看似都是情理之中,document.write
会在执行到时将内容添加到DOM
树中。
而当我们把这条语句放到</body>
或者</html>
之后时,浏览器的做法都是将这条语句提升到</body>
之前执行,下面是谷歌还有火狐,IE的截图。
- chrome
firefox
IE
还有就是将上面的document.write
放在onload
时执行,像下面这样:
Document 页面内容
页面内容
复制代码
结果就是,不论script标签在哪,页面上都只会渲染脚本输出
一句话,所以我们得到下面结论:
- 浏览器解析
DOM
到body
标签为止 body
标签之后的script
会被提升到</body>
之前执行,但仍在onload
事件之前。
后记:
根据个人理解以及整理得,有错误或者偏差敬请原谅,欢迎指正。