前端工程师应该了解--浏览器的工作原理

569 阅读8分钟

**
**

当我们把一串URL输入浏览器之后,依次发生了以下五个步骤:

  1. 浏览器首先使用HTTP或者HTTPS协议,向服务器发送请求;
  2. 把服务器返回的HTML代码经过解析,构建出DOM树;
  3. 计算DOM树上的CSS属性;
  4. 根据CSS属性对元素进行逐个渲染,得到内存中的位图;
  5. 一个可选的步骤是对位图的合成,这可以极大的提高后续绘制的速度;
  6. 合成之后在绘制到界面上;

        从HTTP请求返回后,后续的 DOM 树构建、CSS 计算、渲染、合成、绘制,都是尽可能的流式的处理前一步的产出,也就是之后的过程都不需要等待到上一步完全结束之后再去处理上一步的输出,这样我们在浏览网页时,才会看到逐步出现的页面。

第一阶段:向服务端发送请求

        第一阶段就是浏览器通过HTTP给后端发送请求的过程:

       1.首先网络运营商会给你分配一个IP地址,这个IP地址可能是动态的也可能是静态的,静态 IP是不变的。动态IP是会改变的;

       2.然后我们开始输入一个网址,然后回车;浏览器首先会判断你输入的究竟是一个域名还是一个IP地址,如果是域名的话我们要进行域名的解析,这就需要、本地 DNS、根 DNS、顶级 DNS、权威 DNS 的层层解析,他们中间会有缓存,所以不会消耗太多的时间;

3.解析完成之后会建立TCP/IP连接(三次握手),建立连接之后发送请求http请求(request line,请求头,请求体);然后准备接受服务器的响应

在这个过程中需要注意的是:

对于静态资源DNS可能会解析出CDN地址,导致我们访问的CDN的服务器而不是目标网站的服务器,因为CDN服务器会缓存大多数的静态资源,如图片,样式等等,所以我们不需要请求目标服务器就可以拿到相应的资源,

        如果你请求的资源是一个由后端语言生成的动态资源,那么我们就必须去访问我们的目标服务器,目标网站入口一定是一个负载均衡的设备,之后负载均衡设备会去访问缓存服务器,如果缓存服务器也没有我们想要的资源,我们只好把请求发送到应用服务器上,应用服务器处理完请求之后把结果给返回,同时也可能在缓存服务器中也放置一份;

第二阶段:解析代码并构建DOM树

       第一阶段结束后,假设服务器返回给了我们一段HTML代码,其结构并不复杂,其中百分之90的token种类大约只有标签开始、属性、标签结束、注释、CDATA 节点几种;

首先我们先看一段代码是如何被拆分的:

<p class = 'className'> demo </demo>

        以上的代码是一个标准的标签,token的意义是最小单元,一个标签可以嵌套无数个标签,所以拿一个标签作为token显然是不恰当的(过大);

    所以我们按照最小逻辑单元可以把上面代码的标签拆解为:

  1. 标签开始的开始(<p )

  2. 标签的属性(class = 'className')

  3. 标签开始的结束(>)

  4. 标签的内容(如果还有嵌套的标签就继续拆解token)

  5. 标签的结束(

      当后端源源不断的给我们返回字符流的时候,浏览器需要不断的把字符流拆解成一个个的token,每一次接收字符浏览器都要做一个判断,比如我们首先接受了一个'<p' ,浏览器会判断当前是一个标签的开始,但是浏览器做的每个判断都是和当前状态相关的,比如如果遇到一个class = 'className' 在标签开始之后,我们会判断这个token的类型是一个属性,如果这个token出现在闭合标签之后我们会把它判断成一个文本标签的内容;这样我们就形成了一个状态机;html官方文档总共定义了80多个状态机;这些状态机的原理其实就是把每个特殊字符拆解成一个一个独立状态,然后把这些特殊词汇的所带变的状态连接起来,就形成了一个网状状态结构;我们通过这种方法一步步把字符流拆解成词;

      之后我们通过栈来构建DOM树,其规则如下:

  1. 栈顶元素就是当前元素;
  2. 遇到属性就添加到当前节点;
  3. 遇到文本节点,如果当前节点是文本节点,那就与当前节点合并,如果当前节点不是文本节点就成为当前节点的自节点;
  4. 遇到注释节点,就作为当前节点的子节点;
  5. 遇到开始标签就入栈一个节点;
  6. 当遇到闭合标签就出栈一个节点;

第三阶段:给DOM树添加CSS

        第二阶段结束后我们得到了一个具有属性和节点的DOM树,不过这个DOM树并不完整,因为其不包含样式信息;所以这一阶段我们就需要给这个DOM树添加样式信息;

        之前提到过,除第一阶段外,其他所有阶段的工作方式都是流水线方式的;我们不要构建出完整的DOM树才进行CSS的添加,我们在上一阶段去拿到构建好的元素,然后去检查它匹配到了哪些规则,再根据规则的优先级,做覆盖和调整;

这些规则其实就是我们平时所见到的选择器;因为DOM树的构建是由父到子这样的规律,所以我们的选择器的顺序只能是由父到子;例如后代选择器ul>li; 这种设计规则,可以即保证选择器在 DOM 树构建到当前节点时,已经可以准确判断是否匹配,而不需要去考虑后续的节点;

当我们拿到一个选择器之后,首先我们要把它编译成一个抽象语法树,之后把一个复杂的选择器拆成数段,每当一段条件满足的时候就前进一段,我们拿后代选择器为例:

 ul>li .className {
    background:red
 }
  //当我们ul>li这个元素的时候,我们才会去检查其子代是否匹配 .className这个选择器      

        根据这个规则,我们依次把DOM树上的节点,添加上了CSS属性;

第四阶段:确定元素位置

        第三阶段结束后我们得到了一个具有css属性的DOM树,接下来这个阶段是我们的浏览排版元素的过程;我们在排版的时候会遵循一个文档流的概念,所谓正常文档流用一句话概括就是:**依次排布,排不下了就换到下一行;**下面我们就来看一下正常流下的排版原理:

1.文字以及行内元素

首先我们一般会从某个字体文件中获取某个特定文字的相关信息:

                          

        获取到的信息如上图所示,字排版还受到一些 CSS 属性影响,如 line-height,letter-spacing,world-spacing等等;

display值为inline的行内元素在排版的时候也会被直接排入文字流中,行内元素的主轴方向的margin和border属性也会被计算在当前排版的缩紧距离当中;

2**.盒元素:**

       display的值不等于inline的元素被称为盒元素,都是以盒的形式参与排版,根据盒模型,一个盒具有 margin、border、padding、width/height 等属性,它在主轴方向据的空间是由对应方向的这几个属性之和决定的;

大多数的盒元素可以分为两类:行内盒(display的值带有 inline前缀的盒元素)和块盒;对于行内盒元素,我们首先现在行内部进行排版,之后在确定行的位置,这样我们就可以得到每个行内行元素中元素的位置;而块级盒的排版比较简单,计算出交叉轴的高度就可以;

3.绝对定位元素与浮动元素

绝对定对定位元素和正常文档流的排版无关,只需逐层向上找到父级定位元素即可;根据top / left值和这个绝对定位元素的包含块,来确定这个元素的位置以及大小;

浮动元素比较特殊:浏览器对 float 的处理是先排入正常流,再移动到排版宽度的最左或者最右,之后float元素占据了一块的排版空间;因此数行之内主轴的排版大小收到了影响,直到交叉轴方向的尺寸超过了浮动元素的交叉轴尺寸范围,主轴排版尺寸才会恢复;这就是我们所说的浮动元素所带来的文字环绕效果原理;

第五阶段:渲染合成与绘制

1. 渲染:

浏览器中渲染这个过程,就是把每一个元素对应的盒变成位图。整个渲染过程是十分复杂的,其大致可以分为两类:图形和文字;

图形:盒子的背景,边框,SVG,阴影等都是需要绘制的图形类,这里我们需要一个底层的库去支持;

文字:需要字体库的支持,字体库提供读取字体文字的基本能力,它能通过字符的码点绘制出字型;

2.合成:

        在渲染的过程中,不会把自元素渲染到位图上合成的过程,就是为一些元素创建一个“合成后的位图”(我们把它们称之为合成层),然后把一部分子元素渲染到合成层上;

        为什么只把一部分自元素渲染到合成层上呢?原因是这样的在最开始的部分提到过合成的主要目的是为了性能的优化,我们的目的是最大程度的减少绘制次数;

        如果把所有的自元素全部绘制到一个合成层上,我们一旦通过JS,或者其他方式去改变其中任意一个元素的CSS,这份合成后的位图就失效了,我们需要重新绘制所有的元素。当然如果没有合成层这个过程,只要改变CSS属性,同样也是相当于重新绘制所有的元素;

一般浏览器对于合成层会有一个策略去判断一个元素是否应该被绘制到这个合成层上;浏览器一般会根据元素是否有position、transform 等属性,来判断是否要把这个元素绘制到合成层上;

3.绘制

    绘制是把位图最终绘制到屏幕上,变成肉眼可见的图像的过程,浏览器并不需要用代码来处理这个过程,,浏览器只需要把最终要显示的位图交给操作系统即可。

            至此浏览器就帮助我们把URL绘制成了我们想要的网页了;

本文整理自

程劭非(winter)-重学前端

time.geekbang.org/column/arti…

透视HTTP协议 - 罗剑锋(Chrono)

time.geekbang.org/column/arti…