浏览器的渲染机制及优化

用户在使用浏览器访问一个网站时需要先通过HTTP协议向服务器发送请求,之后服务器返回HTML文件与响应信息。这时,浏览器会根据HTML文件来进行解析与渲染(该阶段还包括向服务器请求非内联的CSS文件与JavaScript文件或者其他资源),最终再将页面呈现在用户面前。

其中网页的渲染都是由浏览器完成的,那么如果一个网站的页面加载速度太慢会导致用户体验不够友好,通过详解浏览器渲染页面的过程来引入一些基本的浏览器性能优化方案,让浏览器更快地渲染你的网页并快速响应从而提高用户体验。

关键渲染路径

浏览器接收到服务器返回的HTMLCSSJavascript字节数据并对其进行解析和转变成像素的渲染过程称为关键渲染路径。包括以下步骤:
  1. 解析HTML,生成DOM树(DOM)
  2. 解析CSS,生成CSSOM树(CSSOM)
  3. 将DOM和CSSOM合并,生成渲染树(Render-Tree)
  4. 计算渲染树的布局(Layout)
  5. 将布局渲染到屏幕上(Paint)

通过优化关键渲染路径即可以缩短浏览器渲染页面的时间。

构建DOM树与CSSOM树

浏览器在渲染页面前需要先构建出DOM树和CSSOM(如果没有DOM树和CSSOM树就无法确定页面的结构与样式,所以这两项是必须先构建出来的)。

DOM

全称为Document Object Model文档对象模型,它是HTMLXML文档的编程接口,提供了对文档的结构化表示,并定义了一种可以使程序对该结构进行访问的方式。

比如JavaScript就是通过DOM来操作结构、样式和内容。DOM将文档解析为一个由节点和对象组成的集合,可以说一个WEB页面其实就是一个DOM

DOM构建过程

浏览器从网络或者硬盘中获取HTML字节数据后会经过一个流程将字节解析成DOM树,流程如下:

  • 编码:先将HTML的原始字节数据转换为文件指定编码的字符;

  • 令牌化:然后浏览器会根据HTML规范来将字符串转换成各种令牌;

    <html><body>这样的标签以及标签中的字符串和属性等都会被转化为令牌,每个令牌具有特殊含义的一组规则。

    令牌记录了标签的开始与结束,通过这个特性可以轻松判断一个标签是否为子标签(假设有<html><body>两个标签,当<html>标签的令牌还没遇到它的结束令牌</html>就遇见了<body>标签令牌,那么<body>就是<html>的子标签)。

  • 生成对象:接下来每个令牌都会被转换成定义其属性和规则的对象,这个对象就是节点对象;

  • 构建完成DOM树构建完成,整个对象集合就像是一棵树形结构。

    为什么DOM是一个树形结构?这是因为标签之间含有复杂的父子关系,树形结构正好可以诠释这个关系,同理CSSOM也是树形结构,层叠样式也含有父子关系。例如:

    div p { font-size: 18px }
    

    会先寻找所有p标签并判断它的父标签是否为div之后才会决定要不要采用这个样式进行渲染。

整个DOM树的构建过程其实就是:

字节->字符->令牌->节点对象->对象模型

在解析DOM过程中,会碰到几类特殊的节点需要特殊处理:

  1. <style><link>标签以及具有内联样式的标签,交给CSSOM生成;

  2. <script>标签。

    Javascript可以操作修改DOM结构,可以操作CSSOM修改节点样式,就会导致了浏览器在解析DOM时候,一碰到<script>就会停止DOM的解析(CSS不会),执行完Javascript再返还控制权。

    事实上,Javascript执行前不仅仅是停止了DOM的解析,它还必须等待CSS的解析完成。当浏览器碰到<script>标签时,发现该元素前面的CSS还未解析完成,就会等它解析完成再去执行。

    Javascript阻塞了DOM的解析,也阻塞了其后的CSS解析,整个解析进程必须等待Javascript的完成才能够继续,这就是JS阻塞页面。一个<script>标签推迟了DOMCSSOM的生成以及以后的所有渲染过程,从性能角度上看,将<script>放在页面底部,也就合情合理了。

CSSOM

全称为Cascading Style Sheets Object Model层叠样式表对象模型,它与DOM树的含义相差不大,只不过他是CSS的对象集合。

CSSOMDOM是两个独立的数据结构。

浏览器解析DOM的时候,遇到了<style>和内联样式时候,会根据样式的声明生成CSSOM,因为他们本身含有样式内容。

而遇到了<link>标签时,浏览器会首先发送请求,待请求成功获取外联样式后,便会解析该外联样式,并就会像生成DOM树一样生成相应的CSSOM树。

由于CSSOM负责储存渲染信息,浏览器就必须保证再合成渲染树之前,CSSOM是完备的,这种完备是指所有的CSS(内联、内部、外部)都已经下载完,并解析完,只有DOMCSSOM解析完全结束,浏览器才会进入下一步的渲染,这就是传说中的CSS阻塞渲染

CSS阻塞渲染意味着,在CSSOM完备前,页面将一直处理白屏状态,这就是为什么样式放在<head>标签中,仅仅是为了更快的解析CSS,保证更快的首次渲染。

需要注意的是,即使没有编写任何样式声明,CSSOM依然会生成,默认生成的CSSOM是浏览器自带默认样式。

构建渲染树

在构建了DOMCSSOM树之后,浏览器只是拥有了两个独立的对象集合,DOM树描述了文档的结构与内容,CSSOM树描述了对文档应用的样式规则,想要渲染出页面,就需要将DOM树和CSSOM树结合在一起,生成渲染树。这颗树就包含了页面所有可见元素及其渲染信息。

  • 浏览器会先从DOM树的根节点开始遍历每个可见节点;

    <script><link><meta>都属于不可视节点,另外,display: none的节点也属于不可视节点,没必要渲染在页面上,值得注意的是visibility: hidden属性并不算是不可见属性,它的语义是隐藏元素,但元素仍然占据着局部空间,所以会被渲染成一个空框。

  • CSSOM中适配可视节点的样式规则;
  • 计算这些样式,将计算值应用到可视节点上;
  • 渲染树构建完成,每个节点都是可见节点并且都含有其内容和对应规则的样式。

计算布局

渲染树构建完毕后,浏览器得到了每个可视节点的内容与其样式,下一步则需要计算每个节点在窗口内的确切位置与大小, 也就是布局阶段

CSS采用了一种叫做盒子模型的思维模型来表示每个节点与其他元素之间的距离,盒子模型包括外边距Margin、内边距Padding、边框Border、内容Content。页面中的每个标签其实都是一个个盒子。

布局阶段会从渲染树的根节点开始遍历,然后确定每个节点对象在页面上的确切大小与位置,布局阶段的输出是一个盒子模型,它会精确地捕获每个元素在屏幕内的确切位置与大小,所有相对的测量值也都会被转换为屏幕内的绝对像素值。

渲染

得到了渲染树及其节点的布局信息,浏览器便可以将最终的页面渲染到屏幕。


渲染阻塞的优化方案

浏览器想要渲染一个页面就必须先构建出DOM树和CSSOM树,如果HTMLCSS文件结构非常庞大与复杂,这显然会给页面加载速度带来严重影响。

所谓渲染阻塞资源,即是对该资源发送请求后还需要先构建对应的DOM树或CSSOM树,这种行为显然会延迟渲染操作的开始时间。HTMLCSSJavaScript都是会对渲染产生阻塞的资源,HTML是必需的,但还可以从CSSJavaScript着手优化,尽可能地减少阻塞的产生。

优化CSS

如果可以让CSS资源只在特定的条件下使用,可以在首次加载时先不进行构建CSSOM树,只有在特定条件下,才会让浏览器进行阻塞渲染然后构建CSSOM

比如:

CSS的媒体查询用来实现某些功能或者场景的,它由媒体类型以及零个或多个检查特定媒体特征状况的表达式组成。

<!-- 没有使用媒体查询,这个css资源会阻塞渲染  -->
<link href="style.css" rel="stylesheet">
<!-- all是默认类型,它和不设置媒体查询的效果是一样的 -->
<link href="style.css" rel="stylesheet" media="all">
<!-- 动态媒体查询, 将在网页加载时计算。
根据网页加载时设备的方向,portrait.css 可能阻塞渲染,也可能不阻塞渲染。-->
<link href="portrait.css" rel="stylesheet" media="orientation:portrait">
<!-- 只在打印网页时应用,因此网页首次在浏览器中加载时,它不会阻塞渲染。 -->
<link href="print.css" rel="stylesheet" media="print">

使用媒体查询可以让CSS资源不在首次加载中阻塞渲染,但不管是哪种CSS资源它们的下载请求都不会被忽略,浏览器仍然会先下载CSS文件

优化Javascript

当浏览器的HTML解析器遇到一个<script>标记时会暂停构建DOM,然后将控制权移交至JavaScript引擎,这时引擎会开始执行JavaScript脚本,直到执行结束后,浏览器才会从之前中断的地方恢复,然后继续构建DOM。每次去执行JavaScript脚本都会严重地阻塞DOM树的构建,如果JavaScript脚本还操作了CSSOM,而正好这个CSSOM还没有下载和构建,浏览器甚至会延迟脚本执行和构建DOM,直至完成其CSSOM的下载和构建。

使用async可以通知浏览器该脚本不需要在引用位置执行,这样浏览器就可以继续构建DOMJavaScript脚本会在就绪后开始执行,这样将显著提升页面首次加载的性能。

<!-- 下面2个用法效果是等价的 -->
<script type="text/javascript" src="demo_async.js" async="async"></script>
<script type="text/javascript" src="demo_async.js" async></script>

优化关键渲染路径

优化关键渲染路径就是在对关键资源、关键路径长度和关键字节进行优化。关键资源越少,浏览器在渲染前的准备工作就越少;同样,关键路径长度和关键字节关系到浏览器下载资源的效率,它们越少,浏览器下载资源的速度就越快。

加载部分HTML

服务端在接收到请求时先只响应回HTML的初始部分,后续的HTML内容在需要时再通过AJAX获得。由于服务端只发送了部分HTML文件,这让构建DOM树的工作量减少很多,从而让用户感觉页面的加载速度很快。

注意,这个方法不能用在CSS上,浏览器不允许CSSOM只构建初始部分,否则会无法确定具体的样式。

压缩

通过对外部资源进行压缩可以大幅度地减少浏览器需要下载的资源量,它会减少关键路径长度与关键字节,使页面的加载速度变得更快。

对数据进行压缩其实就是使用更少的位数来对数据进行重编码。

在对HTMLCSSJavaScript这些文件进行压缩之前,还需要先进行一次冗余压缩。所谓冗余压缩,就是去除多余的字符,例如注释、空格符和换行符。这些字符对于程序员是有用的,毕竟没有格式化的代码可读性是非常恐怖的,但它们对于浏览器是没有任何意义的,去除这些冗余可以减少文件的数据量。在进行完冗余压缩之后,再使用压缩算法进一步对数据本身进行压缩,例如GZIPGZIP是一个可以作用于任何字节流的通用压缩算法,它会记忆之前已经看到的内容,然后再尝试查找并替换重复的内容。)。

HTTP缓存

通过网络来获取资源通常是缓慢的,如果资源文件过于膨大,浏览器还需要与服务器之间进行多次往返通信才能获得完整的资源文件。缓存可以复用之前获取的资源,既然后端可以使用缓存来减少访问数据库的开销,那前端自然也可以使用缓存来复用资源文件。

资源预加载

Pre-fetching是一种提示浏览器预先加载用户之后可能会使用到的资源的方法。

使用dns-prefetch来提前进行DNS解析,以便之后可以快速地访问另一个主机名(浏览器会在加载网页时对网页中的域名进行解析缓存,这样你在之后的访问时无需进行额外的DNS解析,减少了用户等待时间,提高了页面加载速度)。

<link rel="dns-prefetch" href="other.hostname.com">

使用prefetch属性可以预先下载资源,不过它的优先级是最低的。

<link rel="prefetch"  href="/some_other_resource.jpeg">

Chrome允许使用subresource属性指定优先级最高的下载资源(当所有属性为subresource的资源下载完完毕后,才会开始下载属性为prefetch的资源)。

<link rel="subresource"  href="/some_other_resource.js">

prerender可以预先渲染好页面并隐藏起来,之后打开这个页面会跳过渲染阶段直接呈现在用户面前(推荐对用户接下来必须访问的页面进行预渲染,否则得不偿失)。

<link rel="prerender"  href="//domain.com/next_page.html">

参考文献

Web Fundamentals | Google Developers

Flushing the Document Early | High Performance Web Sites

浏览器渲染过程与性能优化

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,884评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,347评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,435评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,509评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,611评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,837评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,987评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,730评论 0 267
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,194评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,525评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,664评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,334评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,944评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,764评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,997评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,389评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,554评论 2 349

推荐阅读更多精彩内容