本节我们从用户角度出发,认识浏览器的渲染机制吧。

浏览器

浏览器的主要功能是展示网页资源,也即请求服务器并将结果显示在浏览器窗口中。
资源的格式一般是 HTML,但也有 PDF、图片等其它各种格式。资源的定位由 URI(Uniform resource Identifier) 来实现。

浏览器的结构

废话少说,先上图: image

  1. 用户接口(User Interface)
    包括地址栏,前进后退,书签菜单等窗口上除了网页显示区域以外的部分。

  2. 浏览器引擎(Browser engine):查询与操作渲染引擎的接口。

  3. 渲染引擎(Rendering engine)。
    负责显示请求的内容,比如请求到HTML, 它会负责解析HTMLCSS并将结果显示到窗口中。

  4. 网络(Networking)。
    用于网络请求, 如 HTTP 请求。它包括平台无关的接口和各平台独立的实现。

  5. UI 后端(UI backend)。
    绘制基础元件,如组合框与窗口。它提供平台无关的接口,内部使用操作系统的相应实现。

  6. JavaScript解释器(JavaScript Interpreter):用于解析执行JavaScript代码。

  7. 数据存储(Data storage)。
    这是一个持久层。浏览器需要把所有数据存到硬盘上,如 cookies。
    新的HTML规范 (HTML5) 规定了一个完整(虽然轻量级)的浏览器中的数据库:web database

以上大概是浏览器的主要结构,作为前端,除了大致都了解一下之外,这节我们主要关注渲染引擎和 JavaScript 解释器。
当然,我们先从头理一下页面请求的过程。

页面请求

当我们去面试的时候,常常会被问一个问题:在浏览器里面输入url,按下 enter 键,会发生什么?
这是个或许平时我们不会思考的问题,不过在了解之后会对整个网页渲染有更好的认识。

当我们按下 enter 键之后,浏览器就会发起一个 HTTP 请求,我们也可以从控制台看到:
image

在这里,我们能看到所有浏览器发起的网络请求,包括页面、图片、CSS 文件、xhr 请求等等,还能看到请求的状态(200成功、404找不到、缓存、重定向等等)、耗时、请求头和内容、返回头和内容,等等等等。 这里涉及太多 http 相关的东西啦,先略过。

这里第一个就是我们的页面请求,我们来看看返回了什么:

image

很明显,这里返回<html>页面,然后浏览器会加载页面,同时页面中涉及的资源也会触发请求下载,包括我们看到的png图片、js文件,这里没有css样式,大概是样式被直出到<html>页面里啦。

当然,这里面没有体现请求发送出去之后的流程,下面是一个完整的 HTTP 请求过程

  • 域名解析(此处涉及DNS的寻址过程)
  • 发起TCP的 3 次握手
  • 建立TCP连接后发起http请求
  • 服务器响应http请求,浏览器得到html代码
  • 浏览器解析html代码,并请求 html 代码中的资源(如jscss、图片等,此处可能涉及HTTP缓存)
  • 浏览器对页面进行渲染呈现给用户(此处涉及浏览器的渲染原理)

关于最后一步,我们先上个图:
image 这是我们的浏览器拿到源代码后,会进行的处理。

上面这些环节,如果你还有哪些不是很清楚的,请抱着强烈的好奇心去把它们探索完成。

浏览器渲染机制

解析

我们亲爱的浏览器会解析三个东西:

  1. HTML/SVG/XHTML。解析这三种文件会产生一个DOM Tree。(渲染引擎)
  2. CSS,解析CSS会产生CSS规则树。(渲染引擎)
  3. Javascript脚本,主要是通过DOM APICSSOM API来操作DOM TreeCSS Rule Tree。(JavaScript 解释器)

解析完成后,浏览器引擎会通过DOM TreeCSS Rule Tree来构造Render Tree

大致流程如下图: image

我们来看看它们都是些啥。

DOM Tree

前面也简单讲过 DOM 树了,这里再从《浏览器的渲染原理简介》偷一下(噢,上面东西也是很多从这偷...参考的):

<html>
  <html>
    <head>
      <title>Web page parsing</title>
    </head>
    <body>
      <div>
        <h1>Web page parsing</h1>
        <p>This is an example Web page.</p>
      </div>
    </body>
  </html>
</html>

上面的这段html会生成这样的一个DOM Tree
image

DOM TreeRender Tree有个很简单的区别:像headerdisplay:none的元素,会在DOM Tree中,但不会添加到Render Tree里。

Render Tree

CSS Rule Tree主要是 Firefox 的产物。
Firefox 基本上来说是通过CSS解析生成CSS Rule Tree,然后通过比对DOM生成Style Context Tree
然后 Firefox 通过把Style Context Tree和其Render Tree(Frame Tree)关联上,就完成了。

Webkit 不像 Firefox 要用两个树来干这个,Webkit 也有Style对象,它直接把这个Style对象存在了相应的DOM结点上了。

建立CSS Rule Tree是需要比照着DOM Tree来的。CSS匹配DOM Tree主要是从右到左解析CSSSelector,这是一个相当复杂和有性能问题的事情。

渲染

解析的角度大概讲完了,下面来从渲染的角度讲讲。

基本流程

渲染的流程基本上如下(黄色的四个步骤):

image

  1. 计算CSS样式
  2. 构建Render Tree
  3. Layout:定位坐标和大小,是否换行,各种 position, overflow, z-index 属性等等
  4. 正式开画

重新Layout
图中有很多连接线,代表Javascript动态修改了DOM属性或是CSS属会导致重新Layout,有些改变不会,就是那些指到天上的箭头,比如,修改后的CSS rule没有被匹配到。

1. 重绘(Repaint)
屏幕的一部分要重画,比如某个 CSS 的背景色变了。但是元素的几何尺寸没有变。

2. 重排(Reflow)
元件的几何尺寸变了(Render Tree的一部分或全部发生了变化,ReflowLayout),需要重新验证并计算Render Tree
HTML使用的是流式布局,如果某元件的几何尺寸发生了变化,需要重新布局,也就叫Reflow

Reflow会从<html>这个root frame开始递归往下,依次计算所有的结点几何尺寸和位置,成本比Repaint的成本高得多的多。

所以我们要注意以下一些操作,因为可能会导致性能降低:

  • 增加、删除、修改DOM结点
  • 移动DOM的位置,或是搞个动画
  • 修改CSS样式
  • Resize窗口(移动端没有这个问题),或是滚动

了解这些以后,我们在写代码的时候就会下意识去比避免啦。当然,现在 MVVM 框架流行,以及 CSS3 普遍之后,手动操作DOM的场景也越来越少啦。

浏览器加载顺序

阻塞的 script 标签

正常的网页加载流程是这样的:

  1. 浏览器一边下载 HTML 网页,一边开始解析
  2. 解析过程中,发现<script>标签
  3. 暂停解析,网页渲染的控制权转交给JavaScript引擎
  4. 如果<script>标签引用了外部脚本,就下载该脚本,否则就直接执行
  5. 执行完毕,控制权交还渲染引擎,恢复往下解析HTML网页

js放在body的最后面,可以避免资源阻塞,同时使静态的html页面迅速显示。
如果外部脚本加载时间很长(比如一直无法完成下载),就会造成网页长时间失去响应,浏览器就会呈现“假死”状态,这被称为“阻塞效应”。
html需要等head中所有的jscss加载完成后才会开始绘制,但是html不需要等待放在body最后的js下载执行就会开始绘制。

css放在head里,可避免浏览器渲染的重复计算。
经过上面的渲染过程,我们知道Layout的计算是比较消耗性能的,所以我们在开始计算Render Tree之前,就把所有的css文件拿到,这样可减少RepaintReflow

参考

结束语

这一节我们主要介绍了浏览器的主要结构、一些解析和渲染的机制。至于js文件的加载顺序、以及js代码的加载顺序等等,这里没有太多的讲解,小伙伴们可以自行去研究一下。

部分文章中使用了一些网站的截图,如果涉及侵权,请告诉我删一下谢谢~
温馨提示喵