【1.5w字】前端面试必问的浏览器渲染,建议精读收藏

常用浏览器内核

IE tridentt内核
Chrome blinkt内核(基于webkit,谷歌与Opera software共同开发)
Firefox geckot内核
Safari webkit内核
Opera blinkt内核(跟Chrome一样),之前为presto

浏览器页面加载过程 (网络篇)image.png

从输入url到得到html的详细过程会发生什么?

在浏览器地址栏输入了百度的网址:
https://www.baidu.com/
  1. 构建请求 浏览器会构建请求行:
// 请求方法是GET,路径为根路径,HTTP协议版本为1.1
GET / HTTP/1.1
  1. 查找强缓存 先检查强缓存,如果命中直接使用,否则进入下一步

  2. DNS解析 浏览器根据DNS服务器得到域名的IP地址。由于我们输入的是域名,而数据包是通过IP地址传给对方的。因此我们需要得到域名对应的IP地址。这个过程需要依赖一个服务系统,这个系统将域名和 IP映射,我们将这个系统就叫做DNS(域名系统)得到具体 IP 的过程就是 DNS解析。另外,如果不指定端口的话,默认采用对应的 IP 的 80 端口

  3. 建立 TCP 连接 这里要提醒一点,Chrome 在同一个域名下要求同时最多只能有 6 个 TCP 连接,超过 6 个的话剩下的请求就得等待。假设现在不需要等待,我们进入了 TCP 连接的建立阶段。首先解释一下什么是 TCP:

    • TCP(Transmission Control Protocol,传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议。建立 TCP连接经历了下面三个阶段:

    • 通过三次握手(即总共发送3个数据包确认已经建立连接)建立客户端和服务器之间的连接。

    • 进行数据传输。这里有一个重要的机制,就是接收方接收到数据包后必须要向发送方确认, 如果发送方没有接到这个确认的消息,就判定为数据包丢失,并重新发送该数据包。当然,发送的过程中还有一个优化策略,就是把大的数据包拆成一个个小包,依次传输到接收方,接收方按照这个小包的顺序把它们组装成完整数据包。

    • 断开连接的阶段。数据传输完成,现在要断开连接了,通过四次挥手来断开连接。

    • 读到这里,你应该明白 TCP 连接通过什么手段来保证数据传输的可靠性,一是三次握手确认连接,二是数据包校验保证数据到达接收方,三是通过四次挥手断开连接。

    • 当然,如果再深入地问,比如为什么要三次握手,两次不行吗?第三次握手失败了怎么办?为什么要四次挥手等等这一系列的问题,涉及计算机网络的基础知识,比较底层,但是也是非常重要的细节,希望你能好好研究一下,另外这里有一篇不错的文章,点击进入相应的推荐文章,相信这篇文章能给你启发

  4. 发送http请求 现在TCP连接建立完毕,浏览器可以和服务器开始通信,即开始发送 HTTP 请求。浏览器发 HTTP 请求要携带三样东西:请求行请求头请求体

  5. 服务器处理、响应请求 HTTP 请求到达服务器,服务器进行对应的处理。最后要把数据传给浏览器,也就是返回网络响应。跟请求部分类似,网络响应具有三个部分:响应行响应头响应体

    • 响应完成之后怎么办?TCP 连接就断开了吗?
    • 不一定。这时候要判断Connection字段, 如果请求头或响应头中包含 Connection: Keep-Alive ,表示建立了持久连接,这样TCP连接会一直保持,之后请求统一站点的资源会复用这个连接。否则断开TCP连接, 请求-响应流程结束。
  6. 浏览器得到返回的内容

window.onload和DOMContentLoaded的区别

window.addEventListener('load', function () {
   
  // 页面全部资源加载完才会执行,包括图片、视频等

})


window.addEventListener('DOMContentLoaded', function () {
   
  // DOM渲染完即可执行,此时图片、视频还没加载完
})

对于第一点,首先要明白几点:

  • 加载资源的形式
    • 加载html的静态资源,比如 <script>、<img>、<link>、<frame>等标签元素加载资源
    • 输入url(或跳转页面)加载html,比如输入网址跳转至页面
  • 加载一个资源的过程
    1. 浏览器根据DNS服务器得到域名的IP地址
    2. 向这个IP的机器发送http(s)请求
    3. 服务器收到,处理并返回http请求,比如返回图片或html代码等
    4. 浏览器得到返回内容
  • 浏览器渲染页面的过程
    1. 根据HTML结构生产DOM Tree(只是一个DOM结构,没有样式)
    2. 根据CSS生产CSSOM (CSS Object model 只是一个样式结构)
    3. 将DOM和CSSOM 整合 成RenderTree(渲染树)
    4. 根据RenderTree开始渲染和展示
    5. 遇到 <script/>时,会执行并阻塞渲染(遇到JS会阻塞渲染,因为JS有权利改变DOM结构,所以得规定先后顺序)

⚠️ 思考: 为何要把css放在head中?
如果不把css放在head中,会导致多次渲染,严重影响用户体验同时会损耗性能。CSS一定要放在head中,等body里面的东西出来前就要加载完

浏览器渲染页面过程与页面优化

由一道面试题引发的思考:从用户输入浏览器输入url到页面最后呈现有哪些过程?一道很常规的题目,考的是基本网络原理,和浏览器加载css,js过程答案大致如下:

  1. 用户输入URL地址
  2. 构建请求
  3. 查找强缓存
  4. 浏览器解析URL解析出主机名或向DNS服务商进行域名解析
  5. 浏览器将主机名转换成服务器ip地址(浏览器先查找本地DNS缓存列表 没有的话 再向浏览器默认的DNS服务器发送查询请求 同时缓存)
  6. 浏览器将端口号从URL中解析出来
  7. 浏览器建立一条与目标Web服务器的TCP连接(三次握手)
  8. 浏览器向服务器发送一条HTTP请求报文
  9. 服务器向浏览器返回一条HTTP响应报文
  10. 关闭连接 浏览器解析文档
  11. 如果文档中有资源 重复6 7 8 动作 直至资源全部加载完毕
    以上答案基本简述了一个网页基本的响应过程背后的原理
    但这也只是一部分,浏览器获取数据的部分,至于浏览器拿到数据之后,怎么渲染页面的,一直没太关注
    所以抽出时间研究下浏览器渲染页面的过程

通过研究,了解一些基本常识的原理:

  1. 为什么要将js放到页脚部分
  2. 引入样式的几种方式的权重
  3. css属性书写顺序建议
  4. 何种类型的DOM操作是耗费性能的

浏览器渲染主要流程 (渲染过程篇)

image.png
不同的浏览器内核不同,所以渲染过程不太一样
WebKit 主流程
image.png

Mozilla的Gecko呈现引擎主流程
image.png

浏览器的加载渲染过程
image.png

由上面图可以看出,虽然主流浏览器渲染过程叫法有区别,但是主要流程还是相同的
Gecko 将视觉格式化元素组成的树称为“框架树”。每个元素都是一个框架。WebKit 使用的术语是“呈现树”,它由“呈现对象”组成。
对于元素的放置,WebKit 使用的术语是“布局”,而 Gecko 称之为“重排”。
对于连接 DOM 节点和可视化信息从而创建呈现树的过程,WebKit 使用的术语是“附加”。
所以可以分析出基本过程:
JavaScript(DOM) -> Composite

  1. 处理 HTML 标记,并且解析HTML生成 DOM 树 DOM Tree
  2. 处理 CSS 标记, 解析出Style Rules CSS Rule Tree
  3. 将前二者关联生成渲染树 Render Tree
  4. Layout 根据Render Tree计算每个节点的几何信息
  5. Painting 根据计算好的信息绘制整个页面,在屏幕上绘制(重绘)各个节点

HTML解析(HTML Parser)

HTML Parser的任务是将HTML标记解析成DOM Tree
这个解析可以参考React解析DOM的过程,
但是这里面有很多别的规则和操作,比如容错机制,识别</br>和<br>等等
感兴趣的可以参考 《How Browser Work》

<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>

经过解析之后的DOM Tree差不多就是

将文本的HTML文档,提炼出关键信息,嵌套层级的树形结构,便于计算拓展。这就是HTML Parser的作用

由于浏览器无法直接理解HTML字符串,因此将这一系列的字节流转换为一种有意义并且方便操作的数据结构,这种数据结构就是DOM树。
DOM树本质上是一个以document为根节点的多叉树
那通过什么样的方式来进行解析呢?

HTML文法的本质

首先,我们应该清楚把握一点: HTML 的文法并不是 上下文无关文法
这里,有必要讨论一下什么是 上下文无关文法
在计算机科学的编译原理学科中,有非常明确的定义:

若一个形式文法G = (N, Σ, P, S) 的产生式规则都取如下的形式:V->w,则叫上下文无关语法。其中 V∈N ,w∈(N∪Σ)*

其中把 G = (N, Σ, P, S) 中各个参量的意义解释一下:

  • N 是非终结符(顾名思义,就是说最后一个符号不是它, 下面同理)集合。
  • Σ 是终结符集合。
  • P 是开始符,它必须属于 N ,也就是非终结符。
  • S 就是不同的产生式的集合。如 S -> aSb 等等。

通俗一点讲,上下文无关的文法就是说这个文法中所有产生式的左边都是一个非终结符。
看到这里,如果还有一点懵圈,我举个例子你就明白了。比如:

A -> B

这个文法中,每个产生式左边都会有一个非终结符,这就是上下文无关的文法。在这种情况下,xBy一定是可以规约出xAy的。
我们下面看看看一个反例:

aA -> B
Aa -> B

这种情况就是非上下文无关文法,当遇到B的时候,我们不知道到底能不能规约出A,取决于左边或者右边是否有a存在,也就是说和上下文有关。

关于它为什么是非上下文无关文法,首先需要让大家注意的是,规范的 HTML 语法,是符合上下文无关文法的,能够体现它非上下文无关的是不标准的语法。在此我仅举一个反例即可证明。

比如解析器扫描到form标签的时候,上下文无关文法的处理方式是直接创建对应 form 的 DOM 对象,而真实的 HTML5 场景中却不是这样,解析器会查看 form 的上下文,如果这个 form 标签的父标签也是 form, 那么直接跳过当前的 form 标签,否则才创建 DOM 对象。

常规的编程语言都是上下文无关的,而HTML却相反,也正是它非上下文无关的特性,决定了HTML Parser并不能使用常规编程语言的解析器来完成,需要另辟蹊径。

解析算法

HTML5 规范详细地介绍了解析算法。这个算法分为两个阶段:

  • 标记化 (词法分析)
  • 建树 (语法分析)
标记化算法

这个算法输入为HTML文本,输出为HTML标记,也成为标记生成器。其中运用有限自动状态机来完成。即在当当前状态下,接收一个或多个字符,就会更新到下一个状态。

<html>
  <body>
    Hello sanyuan
  </body>
</html>

通过一个简单的例子来演示一下标记化的过程。
遇到 <, 状态为标记打开
接收 [a-z] 的字符,会进入标记名称状态
这个状态一直保持,直到遇到>,表示标记名称记录完成,这时候变为数据状态
接下来遇到body标签做同样的处理。
这个时候html和body的标记都记录好了。
现在来到中的 >,进入数据状态,之后保持这样状态接收后面的字符hello sanyuan
接着接收 中的 <,回到标记打开, 接收下一个/后,这时候会创建一个 end tag 的token。
随后进入标记名称状态, 遇到 > 回到数据状态
接着以同样的样式处理 。

建树算法

之前提到过,DOM 树是一个以document为根节点的多叉树。因此解析器首先会创建一个document对象。标记生成器会把每个标记的信息发送给建树器建树器接收到相应的标记时,会创建对应的 DOM 对象。创建这个DOM对象后会做两件事情:

  • 将DOM对象加入 DOM 树中。
  • 将对应标记压入存放开放(与闭合标签意思对应)元素的栈中。
<html>
  <body>
    Hello sanyuan
  </body>
</html>

首先,状态为初始化状态
接收到标记生成器传来的 html 标签,这时候状态变为before html状态。同时创建一个 HTMLHtmlElement 的 DOM 元素, 将其加到document根对象上,并进行压栈操作。
接着状态自动变为before head, 此时从标记生成器那边传来body,表示并没有head, 这时候建树器会自动创建一个HTMLHeadElement并将其加入到DOM树中。
现在进入到in head状态, 然后直接跳到after head
现在标记生成器传来了body标记,创建HTMLBodyElement, 插入到DOM树中,同时压入开放标记栈。
接着状态变为in body,然后来接收后面一系列的字符: Hello sanyuan。接收到第一个字符的时候,会创建一个Text节点并把字符插入其中,然后把Text节点插入到 DOM 树中body元素的下面。随着不断接收后面的字符,这些字符会附在Text节点上。
现在,标记生成器传过来一个body的结束标记,进入到after body状态。
标记生成器最后传过来一个html的结束标记, 进入到after after body的状态,表示解析过程到此结束。

容错机制

讲到HTML5规范,就不得不说它强大的宽容策略, 容错能力非常强,虽然大家褒贬不一,不过我想作为一名资深的前端工程师,有必要知道HTML Parser在容错方面做了哪些事情。
接下来是 WebKit 中一些经典的容错示例,发现有其他的也欢迎来补充。

  1. 使用的 <br>全部换为 <br/>的形式
if (t->isCloseTag(brTag) && m_document->inCompatMode()) {
   
  reportError(MalformedBRError);
  t->beginTag = true;
}
  1. 表格离散
<table>
  <table>
    <tr><td>inner table</td></tr>
  </table>
  <tr><td>outer table</td></tr>
</table>

WebKit会自动转换为:

<table>
    <tr><td>outer table</td></tr>
</table>
<table
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值