浏览器是如何工作的?

411 阅读7分钟

对浏览器的实现者来说,他们做的事情,就是把一个 URL 变成一个屏幕上显示的网页。这个过程是这样的:

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

在开始详细介绍之前,要建立一个感性认识。从 HTTP 请求回来开始,这个过程并非一般想象中的一步做完再做下一步,而是一条流水线。从 HTTP 请求回来,就产生了流式的数据,后续的 DOM 树构建、CSS 计算、渲染、合成、绘制,都是尽可能地流式处理前一步的产出:即不需要等到上一步骤完全结束,就开始处理上一步的输出,这样在浏览网页时,才会看到逐步出现的页面。

1)、HTTP 协议

浏览器首先要做的事就是根据 URL 把数据取回来,取回数据使用的是 HTTP 协议,实际上这个过程之前还有 DNS 查询,不过这里就不详细展开了。

  • HTTP 标准:由 IETF 组织制定,跟它相关的标准主要有两份:
    • HTTP1.1 tools.ietf.org/html/rfc261…
    • HTTP1.1 tools.ietf.org/html/rfc723…
    • 协议是基于 TCP 协议出现的,对 TCP 协议来说,TCP 协议是一条双向的通讯通道,HTTP 在 TCP 的基础上,规定了 Request-Response 的模式。这个模式决定了通讯必定是由浏览器端首先发起的。
    • 大部分情况下,浏览器的实现者只需要用一个 TCP 库,甚至一个现成的 HTTP 库就可以搞定浏览器的网络通讯部分。HTTP 是纯粹的文本协议,它是规定了使用 TCP 协议来传输文本格式的一个应用层协议。
  • HTTP 协议格式
  • HTTP Method(方法)
    • GET:浏览器通过地址栏访问页面都是 GET 方法
    • POST:表单提交产生 POST 方法
    • HEAD:HEAD 则是跟 GET 类似,只返回请求头,多数由 JavaScript 发起
    • PUT:添加资源
    • DELETE:删除资源
    • CONNECT:现在多用于 HTTPS 和 WebSocket
    • OPTIONS:一般用于调试,多数线上服务都不支持
    • TRACE:一般用于调试,多数线上服务都不支持
  • HTTP Status code(状态码)和 Status text(状态文本)
    • 1xx:临时回应,表示客户端请继续,1xx 系列的状态码是非常陌生的,原因是 1xx 的状态被浏览器 HTTP 库直接处理掉了,不会让上层应用知晓。
    • 2xx:请求成功。200:请求成功。
    • 3xx: 表示请求的目标有变化,希望客户端进一步处理。
      • 301&302:永久性与临时性跳转。
      • 304:跟客户端缓存没有更新,:客户端本地已经有缓存的版本,并且在 Request 中告诉了服务端,当服务端通过时间或者 tag,发现没有更新的时候,就会返回一个不含 body 的 304 状态。
    • 4xx:客户端请求错误。
      • 403:无权限。
      • 404:表示请求的页面不存在。
      • 418:It’s a teapot. 这是一个彩蛋,来自 ietf 的一个愚人节玩笑。(超文本咖啡壶控制协议)
    • 5xx:服务端请求错误。
      • 500:服务端错误。
      • 503:服务端暂时性错误,可以一会再试。
  • HTTP Head (HTTP 头)
    • HTTP 头可以看作一个键值对。原则上,HTTP 头也是一种数据,我们可以自由定义 HTTP 头和值。不过在 HTTP 规范中,规定了一些特殊的 HTTP 头,我们现在就来了解一下它们。在 HTTP 标准中,有完整的请求 / 响应头规定,这里我们挑几个重点的说一下:
    • Request Heade
    • Response Header
  • HTTP Request Body
    • HTTP 请求的 body 主要用于提交表单场景。实际上,HTTP 请求的 body 是比较自由的,只要浏览器端发送的 body 服务端认可就可以了。一些常见的 body 格式是:
      • application/json
      • application/x-www-form-urlencoded
      • multipart/form-data
      • text/xml
    • 我们使用 HTML 的 form 标签提交产生的 HTML 请求,默认会产生 application/x-www-form-urlencoded 的数据格式,当有文件上传时,则会使用 multipart/form-data。
  • HTTPS
    • HTTPS 在 HTTP 协议的基础上,HTTPS 和 HTTP2 规定了更复杂的内容,但是它基本保持了 HTTP 的设计思想,即:使用上的 Request-Response 模式。
    • 我们首先来了解下 HTTPS。HTTPS 有两个作用,一是确定请求的目标服务端身份,二是保证传输的数据不会被网络中间节点窃听或者篡改。
    • HTTPS 的标准也是由 RFC 规定的,你可以查看它的详情链接:tools.ietf.org/html/rfc281…
    • HTTPS 是使用加密通道来传输 HTTP 的内容。但是 HTTPS 首先与服务端建立一条 TLS 加密通道。TLS 构建于 TCP 协议之上,它实际上是对传输的内容做一次加密,所以从传输内容上看,HTTPS 跟 HTTP 没有任何区别。
  • HTTP 2
    • HTTP 2 是 HTTP 1.1 的升级版本,你可以查看它的详情链接。tools.ietf.org/html/rfc754…
    • HTTP 2.0 最大的改进有两点,一是支持服务端推送,二是支持 TCP 连接复用。
    • 服务端推送能够在客户端发送第一个请求到服务端时,提前把一部分内容推送给客户端,放入缓存当中,这可以避免客户端请求顺序带来的并行度不高,从而导致的性能问题。
    • TCP 连接复用,则使用同一个 TCP 连接来传输多个 HTTP 请求,避免了 TCP 连接建立时的三次握手开销,和初建 TCP 连接时传输窗口小的问题。
    • Note: 其实很多优化涉及更下层的协议。IP 层的分包情况,和物理层的建连时间是需要被考虑的。

2)、解析代码

HTML 的结构不算太复杂,我们日常开发需要的 90% 的“词”(指编译原理的术语 token,表示最小的有意义的单元),种类大约只有标签开始、属性、标签结束、注释、CDATA 节点几种。

  • 词(token)是如何被拆分的:首先来看看一个非常标准的标签,会被如何拆分:
    <p class="a">text text text</p>
    
    • 如果我们从最小有意义单元的定义来拆分,第一个词(token)是什么呢?显然,作为一个词(token),整个 p 标签肯定是过大了(它甚至可以嵌套)。
    • 那么,只用 p 标签的开头是不是合适吗?我们考虑到起始标签也是会包含属性的,最小的意义单元其实是“<p” ,所以“ <p” 就是我们的第一个词(token)。
    • 我们继续拆分,可以把这段代码依次拆成词(token):
      • <p“标签开始”的开始;
      • class=“a” 属性;
      • “标签开始”的结束;

      • text text text 文本;
      • 标签结束。
    • 这是一段最简单的例子,类似的还有什么呢?现在我们可以来来看看这些词(token)长成啥样子:
  • 状态机: 绝大多数语言的词法部分都是用状态机实现的。那么我们来把部分词(token)的解析画成一个状态机看看:
  • 构建 DOM 树:接下来我们要把这些简单的词变成 DOM 树,这个过程我们是使用栈来实现的,任何语言几乎都有栈。
  • 载入css样式