面试常问:输入url到页面渲染展示流程

以请求http://www.baidu.com为例

1.构建请求


浏览器通过输入的url来解析要请求的是什么协议,构建请求报文,这里要构建的就是HTTP请求报文
HTTP请求报文包括报文首部和报文主体,对请求报文来说,报文首部包括请求行和各种首部字段,而对响应报文来说,报文首部包括状态行和各种首部字段

请求行:GET/HTTP/1.1 (请求方法和相应的协议)
状态行:HTTP/1.1 200 OK (相应的协议和状态码)
各种首部字段:诸如上文提到的Cache-Control和Expires,还有If-No-Match,If-modified-since,Etag等
报文主体:在get请求中没有,post请求中向服务器发出的数据,响应报文中为服务器返回给客户端的内容

下面就是www.baidu.com对应的请求头首部字段
在这里插入图片描述

2. 强/本地缓存的查找


通过请求报文,找到本地相应资源的响应报文,查看expires或cache-control来判断是否使用强缓存
如果缓存资源还没有过期,那么就使用本地缓存的资源,返回状态码304,如果资源已过期,那么接着执行
具体内容移步强缓存和协商缓存

3. DNS解析


我们请求的时候,请求的地址是IP地址,所以要将我们写的url地址转换为IP地址,就需要DNS解析
在解析的时候,首先查看本地DNS服务器是否有相应域名对应的IP地址,如果有的话,直接返回请求该IP地址
如果没有的话,根据该地址向根域名服务器发起请求,获取对应的顶级域名服务器的地址,向该顶级域名服务器发起请求,获得对应的权限域名服务器对应的地址,然后返回最终的IP地址
返回的时候,不仅会返回到要请求的地方,还将该地址缓存到本地DNS服务器

4. 建立TCP连接


获得对应的IP地址后,就等于我们知道了服务器所在的地方,那么对于HTTP请求来说,下一步就是发起TCP连接了
首先,我们要进行三次握手建立TCP连接

5. 发起HTTP请求


在TCP连接建立好之后,就可以从浏览器向服务端发送HTTP请求报文了

准备好HTTP请求报文后,就向服务器发起HTTP请求,然后等待服务器返回响应报文

在这个过程中,根据该HTTP连接的方式,会有不同的处理方式

短连接

如果是短连接的话,在这次HTTP请求发出得到响应后,便会四次挥手关闭TCP连接,在下一次要连接时重新建立HTTP连接,这是HTTP1.0中默认的连接方式

长连接

如果是长连接的话,在这次HTTP请求发出得到响应后,不会关闭TCP连接,而是会继续通过该连接,发起HTTP请求。当然,可以通过和服务器协商,关闭这个连接,可以通过connection值是否为Keep-Alive来判断,若服务器想明确断开连接的话,将connection的值设为close就可以了
然而,长连接存在一定的问题,同一个连接中的请求,是不能并行发起的,也即是说,只能等上一个请求得到响应后才能发起下一个请求,如果其中一个请求需要很长的时间,那么就会阻塞所有请求
当然,我们可以采用发起并行的HTTP连接来解决这个问题,这样我们可以同时发起几个请求,但始终还是会造成多次握手,而且请求的并行量也只限于建立的HTTP连接数
长连接是HTTP1.1中默认的连接方式,若想在HTTP1.0中使用,只要给connection字段添加Keep-Alive值就可以了

连接复用

在HTTP2.0中,出现了连接复用,允许在一个TCP连接中发起并行的HTTP请求,通过这种方式,解决了长连接出现的问题

6. 协商缓存


在发起HTTP请求后,我们所获取的资源还不一定是最新的资源,要先通过协商缓存来判断是否使用缓存
通过在请求中添加If-Modified-Since字段和If-None-Match字段来向服务器确认当前资源是否继续使用缓存
具体内容移步强缓存和协商缓存

7. 获取响应

通过强缓存,协商缓存,最后不管是在缓存,还是在一些缓存的服务器上,又或者是在源服务器上,我们最终拿到了响应资源,将其渲染到页面上
在这里插入图片描述
请求http://www.baidu.com实际上就返回了这样的html文本
如果是在HTTP2.0中,因为有服务端推送的功能,所以可能当我们去请求一个html的时候,会将这个html引用到的css文件和js文件都通过服务端推送到客户端,这样请求的次数也会减少

8. 构建DOM树

因为我们请求的是一个html文件,而实际上,浏览器无法直接理解和使用html,所以需要将其解析为浏览器能理解的解构—DOM树
我们可以试着将下面的这部分html内容来转换成DOM树试试

<html>
    <head>
        <link href="index.css" rel="stylesheet">
    </head>
    <body>
        <p><span>重点介绍</span>渲染过程</p>
        <div>
            <div>div</div>
            <p>p</p>
        </div>
    </body>
</html>


这里蓝色背景的是DOM节点,而其他的则是节点内容
此外,我们也可以通过在控制台console里面输入document来观察这颗DOM树

9. 样式计算

样式计算是为了计算出DOM节点中每个元素的具体样式

1. 把css转换成浏览器能理解的结构

首先我们先确定一下,css的样式从哪里获取,有以下四种

  • 标签自带的样式
  • 通过link/@import引用的css文件
  • style标签内的css样式
  • 元素内嵌的css样式

当渲染引擎接收到这些css样式之后,会将这些css样式转换为stylesheets
我们可以通过在控制台console里面使用document.stylesheets来查看相关结构

2. 转换样式表中的属性值,使其标准化

对于样式中em这样的属性值,或者font-weight的bold,color的red等,这些都不容易被渲染引擎理解,所以要将其转换成标准值,比如2em在font-size为16px的时候,2em就会转换成32px,bold转换成一个具体的数值,red转换城rgb(255,0,0)这样

3. 计算每个DOM的具体样式

我们上面也说过了,样式的获取有四个来源,而在这四个来源中,可能会对同一个元素的样式进行重复的设置,所以我们需要在这一步去具体计算每个元素的具体样式
关于具体样式,就涉及到了css的继承规则和层叠规则了

CSS继承

首先是CSS继承,我们知道,在css中,父元素的样式,会直接做为子元素的样式,比如下面的样式

body{
    font-size:20px;
}

如果我们没对body里的子元素做font-size样式的设置的话,就会采用在这里body设置的样式
我们可以通过在控制器里面的element标签页,选中一个元素,在下方的styles标签页查看具体的样式信息,除了样式的值外,还能看到样式具体是在哪个文件里设置的,以及哪些样式被无效化/覆盖(被划了一条横线)了

10. 布局阶段

1. 创建布局树

在得到了DOM节点树和stylesheets之后,我们就具备了构建布局树的条件,在构建布局树的时候,我们需要结合DOM节点树和stylesheets,来遍历所有可见的节点,将其添加到布局树中

在创建布局树时,要明确哪些元素是不会被添加进去的,比如head标签下的所有内容,都不会添加到布局树中,以及display样式为none的元素也不会被添加到布局树中

2. 布局计算

有关于布局的计算,不只是简单的二维位置上的计算,还涉及到“三维”的计算
我们知道,在页面上,我们是只能看到一个二维的显示的,但是实际上,我们也能看到很多元素是存在重叠的,那么,我们怎么去绘制这种重叠呢,就需要在垂直于页面的z轴上做一个分层了

分层

层叠上下文引起的分层

我们首先要了解一下层叠上下文的概念
这里就简单地说一下有哪些我们常见的层叠上下文的情况

  • html根标签,这是做为一个最基础的一个层级
  • position为absolute或者relative的且z-index不为auto的元素,设置这个样式的元素,在绘制时会覆盖在我们文档流的上方,所以也会提升为一个层
  • opacity小于1的元素,我们平时在写的时候能看到,如果样式呈现透明的话,我们是能看到背景的内容的,所以这也是一个提升层,同样地,filter设置的透明度也是一样的道理
  • transform设置translateZ为一个不为0的数值的时候,这个非常直观,直接相对于本身所处的层级进行一个z轴的移动,在布局计算分层时会被提升为一个新的层

实际上,还有其他情况,这里主要是要讲分层内容,所以不详细描述了,具体资料详见:MDN

通过上面举出的层叠上下文我们可以看到,凡是会引起重叠,或者在z轴上进行了移动的内容,都会引起分层

这也得出一个结论,在浏览器进行分层的时候,每个带有层叠上下文的元素都会被提升为一个单独的层

为了更直观地看到效果,我们通过一段代码和chrome开发者工具来查看分层

<html>

<head>
    <style>
        .transform {
            transform: translateZ(10px);
        }
    </style>
</head>

<body>
    <p><span>重点介绍</span>渲染过程</p>
    <div>
        <div>div</div>
        <p class="transform">p</p>
    </div>
</body>

</html>

打开浏览器控制台,输入ctrl+shift+p,在搜索框内输入layers,选择show Layers

然后我们进入这个选项卡,可以看到这样的界面

这里在左边我们已经看到有两个分层了,document实际上就是html根元素的一个层,下面的p.transform就是使用了transform: translateZ(10px);样式的层,为了更直观地观察,我们选择可选操作第二个,旋转一下我们看的角度,将其转到侧面

此时我们可以明确地看到,确实是被分成了两层,通过修改transform: translateZ(10px);里面的移动值,可以改变两个层之间的距离

剪裁引起的分层

在页面出现剪裁的时候,同样会引起分层,我们举个例子

<html>

<head>
    <style>
        div {
            width: 100px;
            height: 100px;
            overflow: auto;
        }
    </style>
</head>

<body>
    <div>
        <p>这里我们给外部的div标签设置了一个width为100px,height为100px的宽高</p>
        <p>同时,为了实现一个剪裁,我们将其overflow设置为auto,使其出现滚动条效果</p>
    </div>
</body>

</html>

同样使用layers来查看分层,进入layers,同样转移到侧面视角,可以看到四个分层

那么,这四个分层对应的是什么呢,首先,和之前的例子一样,#document是html根元素所在的层,而此外的三个层,都出自div,这三个层分别是

  • 显示内容的层
  • 完整内容的层
  • 滚动条的层

我们可以将视角再偏移一下,就会更加明显了

这里我们要确定哪个是完整内容,其实拖动一下滚动条就行了,会发现中间的层在上下移动

11. 生成图层绘制列表

到了这一步,图层已经分层好了,我们也能确定要绘制的内容的具体位置,那么我们怎么去进行一个绘制呢

其实原理相当简单,举一个例子

上面这个图片要怎么绘制呢,首先,我们要绘制一个黑色的圆,然后绘制一个黄色的五角星,最会绘制一个红色的六边形
其实结果已经出来了,在浏览器渲染的时候,就是先从离我们“远”的那个图层开始绘制起,渲染机制通过将这些层做为一个个任务,每层对应一个任务,放在一个任务队列里,依次绘制这些图层

要观察这个过程,我们同样用到chrome浏览器开发者工具的Layers
用回我们上面的第一个例子,来到#document层,选择Paint Profiler,打开Profiler界面,可以看到绘制的过程

通过下图可见,绘制过程是从“远”的图层到“近”的图层

在拖动右下角的区域的进度条的时候,我们同样可以看到,绘制是是从上到下绘制的

但是到这一步,也只是生成图层绘制列表而已,还没真正开始绘制

12. 栅格化操作

实际上,绘制是由渲染引擎中的合成线程来完成的。
在绘制列表提交到合成线程之后,合成线程会将图层分成多个图块,这些图块的大小通常是256x256或者512x512
生成图块的优先级与我们当前的视口有关(一个页面中用户当前能看到的内容),合成线程会针对视口附近的图块优先生成位图,实际生成位图的操作是由栅格化来实现的。而图块是栅格化的最小单位。

通常,栅格化过程都会使用GPU来加速生成,使用GPU生成位图的过程叫快速栅格化,或者GPU栅格化,生成的位图被保存在GPU内存中。

GPU操作是运行在GPU进程中,如果栅格化操作使用了GPU,那么最终生成位图的操作是在GPU中完成的,这就涉及到了跨进程操作。

13. 合成与显示

一旦所有的图块都被光栅化,合成线程就会生成一个绘制图块的命令,DrawQuad,然后将该命令提交给浏览器进程

浏览器进程里面有一个叫viz的组件,用来接收合成线程发过来的DrawQuad命令,然后根据DrawQuad命令,将其页面内容绘制到内存中,最后再将内存显示在屏幕上。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值