网页的渲染机制
参考文章:
ScriptJava——了解HTML页面的渲染过程
浏览器加载,解析,渲染的过程
HTML渲染过程详解
Web页面加载,解析,渲染过程
浏览器的渲染机制
还有最经典的
How browsers work
前端必读:浏览器内部工作原理
既然谈到了网页的渲染机制,一般而言指的就是浏览器下网页的渲染过程了,也就是客户端的浏览器行为。为什么我这里要提到客户端,因为按照知识点延伸,知识的枝丫还可以“生长”到HTTP的知识点,当然这里就不展开啦。
浏览器构成
浏览器主要组件-
用户界面(User Interface)
包括地址栏、后退/前进按钮、书签目录等,也就是除了显示你请求的页面以外的所有部分
-
浏览器引擎(Browser engine)
用来查询及操作渲染引擎的接口,如我们常提到的Webkit(Safari,Chrome),Gecko(Firefox),Trident(IE)等。 -
渲染引擎(Rendering engine)
用来显示请求的内容,例如:请求内容为HTML,它负责解析HTML及CSS,并将解析后的结果显示出来。 -
网络层(Networking)
用来完成网络调用,例如http请求,它具有平台无关的接口,可以在不同的平台上工作。 -
UI后端(UI Backend)
用来绘制类似组合选择框及对话框等基本组件,具有不特定于某个平台的通过接口,底层使用操作系统的用户接口。
P.S:调用的是操作系统的底层GUI接口? -
JS解释器(JS编译器)
用来解释JS代码。 -
数据存储(Data Persistence)
于持久层,浏览器需要在硬盘中保存类似cookie的各种数据,HTML5定义了web database技术,这是一种轻量级完整的客户端存储技术。
需要注意的是,不同于大部分浏览器,Chrome为每个Tab分配了各自的渲染引擎实例,每个Tab就是一个独立的进程。
难道这是Chrome耗内存的原因之一?类比了一下JVM......
Webkit主流程 (firefox)Geoko主流程
渲染主流程(The main flow)
-
当向service请求到资源后,交由渲染引擎以解析html,并将标签转化为DOM树(Document Object Model Tree),什么是DOM树。
DOM树的构建过程是一个深度遍历的过程:当前节点的所有子节点都构建好后才会去构建当前节点的下一个兄弟节点。 -
将CSS解析成CSSOM树(Cascading Style Sheets Object Model Tree)
-
根据DOM树和CSSOM树去构造Rendering Tree(渲染树)。
渲染树由一些包含有颜色和大小等属性的矩形组成,它们将被按照正确的顺序显示到屏幕上。构建好渲染树后,将会执行布局过程,它将确定每个节点在屏幕上的确切坐标。在下一步就是绘制,即遍历渲染树,并使用UI后端层绘制每个节点。
值得注意的事,这个过程是逐步完成的,为了更好的用户体验,渲染引擎将会尽可能早的将内容呈现到屏幕上,并不会等到所有的html都解析完成之后再去构建和布局渲染树。它是解析完一部分内容就显示一部分内容,同时,可能还在通过网络下载其余内容。
这里基本上就是粗略的梳理了一遍浏览器解析过程,DOM、CSSOM、Rendering等,但是还有一些point需要知道的:
脚本
web的模式是同步的,开发者希望解析到一个script标签时立即解析执行脚本,并阻塞文档的解析直到脚本执行完。如果脚本是外引的(
src
),则网络必须先请求到这个资源——这个过程也是同步的,会阻塞文档的解析直到资源被请求到。这个模式保持了很多年,并且在html4及html5中都特别指定了。开发者可以将脚本标识为defer,以使其不阻塞文档解析,并在文档解析结束后执行。Html5增加了标记脚本为异步的选项,以使脚本的解析执行使用另一个线程。
这里又会有新的知识点,浏览器渲染中的进程阻塞,后面再说。
预解析(Speculative parsing)
Webkit和Firefox都做了这个优化,当执行脚本时,另一个线程解析剩下的文档,并加载后面需要通过网络加载的资源。这种方式可以使资源并行加载从而使整体速度更快。需要注意的是,预解析并不改变Dom树,它将这个工作留给主解析过程,自己只解析外部资源的引用,比如外部脚本、样式表及图片。
样式表(Style sheets)
样式表采用另一种不同的模式。理论上,既然样式表不改变Dom树,也就没有必要停下文档的解析等待它们,然而,存在一个问题,脚本可能在文档的解析过程中请求样式信息,如果样式还没有加载和解析,脚本将得到错误的值,显然这将会导致很多问题,这看起来是个边缘情况,但确实很常见。Firefox在存在样式表还在加载和解析时阻塞所有的脚本,而Chrome只在当脚本试图访问某些可能被未加载的样式表所影响的特定的样式属性时才阻塞这些脚本。
进程阻塞
要实现一个阻塞,最简单的方法,就是在HTML中head
内出插入一段<script>alert("hello world")</script>
。
这就是一个阻塞案例。
我们回顾一下浏览器渲染过程,从URL输入地址栏,DNS解析,服务器获取请求.......到最后浏览器拿到HTTP响应和响应内容,开始解析。
这时候,渲染引擎就会开始解析HTML标签,此时,会有CSS的link
,图片的src
等等资源加载请求被发出,但这些,都不会影响“主线程”HTML文档的解析,是异步请求。
但是,当遇到了js文件,不管是href还是内嵌式的脚本,HTML都会挂起渲染(加载解析渲染同步)的线程,不仅要等待js文件加载完毕,还要等待解析,执行完毕,才可以恢复HTML文档的渲染过程。
那一般来说,我们希望JS什么时候执行呢?首先我们得想JS对HTML和CSS有什么作用,没错,经常出现的就是通过JS增删查改HTML和CSS,添加DOM节点,改变式样等等,那么我们就得保证JS执行之前,浏览器中已经解析完毕所有的HTML和CSS,这样JS才能“找”到它们,so,一般来说,都是将JS放在最后执行(只是标准情况)。
原因:
JS有可能会修改DOM,最为经典的document.write,这意味着,在JS执行完成前,后续所有资源的下载可能是没有必要的,这是js阻塞后续资源下载的根本原因。
办法:可以将外部引用的js文件放在</body>前。
这里也有一个新问题:CSS和JS在网页中的放置顺序是怎样的?
CSS虽然不影响JS文件的加载,但却影响JS的执行,如脚本中需要用到CSS,但CSS又没有解析完成时......
原因:
可能会有var width = $('#id').width()
,这意味着,js代码执行前,浏览器必须保证css文件已下载和解析完成。这也是css阻塞后续js的根本原因。
办法:当js文件不需要依赖css文件时,可以将js文件放在头部css的前面。
图片来自——ScriptJava的Blog
知识点挺多,引用几篇文章。
网友评论