一个页面的半生

319 阅读8分钟

一个页面的半生

楔子

作为前端方向的我们,对页面的性能优化是我们看家本领之一.

页面也是我们日常打交道非常多的一部分.所以在做性能优化前,对页面的生命周期有一个全面的了解是非常有必要的.

本篇作为性能优化系列之第一篇也是出于这个目的.

页面的生命周期从大方向上来看分为三个阶段:

  1. 导航阶段

  2. 加载渲染阶段

  3. 运行阶段

而今个儿是页面的半生,同时我们平常所称的性能优化大部分情况也是指优化页面加载,所以本篇只谈前两个阶段.

(注: 为了避免引入更高复杂度,不细述浏览器多进程模型各个进程的具体分工.有兴趣的朋友可以自己搜索,或者等我填坑 :) )

导航阶段

导航阶段顾名思义也就是: 从输入URL到页面开始解析HTML文档解析.

具体过程如下:

  1. 从用户在地址栏输入开始

  2. 浏览器接收到输入开始解析内容.如果是网页地址则进入下一步

  3. 查找资源缓存,如果有直接返回.没有进入下一步

  4. 根据输入进行DNS的解析,找到对应的服务器ip.

  5. 和服务器ip地址建立TCP链接

  6. 发送HTTP/HTTPS请求.

  7. 接收到返回解析数据.如果正常收到HTML文档.则进入下一步

  8. 提交渲染.

  9. 导航阶段结束

这里我们合成为三个阶段谈下:

1,2之跳转输入校验

用户在地址栏输入后,首先浏览器会根据输入格式进行判断:

  • 如果符合URL的规则,如: .com结尾等,会加上协议(HTTPS/HTTP)合成为URL.

  • 如果不符合则作为搜索内容合成搜索引擎的搜索链接进行搜索.或者进入某些内置页面.

再进入下一个阶段之前,还会触发一次beforeunload事件.

如果代码在beforeunload中调用event.preventDefault()会弹出一个确认框,让用户确认是否离开当前页面.如果取消则停留在当前页面.确定则正常进入下一个阶段.

3,4,5,6,7之URL请求

进入这一阶段后,首先会去查找缓存,如果有就直接返回.

如果没有,则应该向服务器请求了.

首先递归或者遍历的进行DNS解析,得到机器的ip地址.之后根据ip地址建立对应的TCP链接.如果是HTTPS,还需要进行TLS验证

组合请求后发送对应请求.同时会在请求发起前对跨域进行校验.

当URL请求返回后,会根据请求的返回做一些处理:

  • 如果是301,302等重定向状态码

此情况会进入重定向流程,从3开始重新执行

  • 如果是200,但是Content-Type不是text/html.

如果是Content-Type代表字节流(application/octet-stream)会当作下载来处理

  • 如果是状态码是200,且Content-Type是text/html.

则正常进入下一步.

8,9之提交渲染

接收到返回后.浏览器会把旧的HTML移除掉,同时更新导航栏,前进后退等界面状态.同时开始解析加载新的HTML.也就是我们切换页面看到的白屏事件.

只此导航阶段结束,进入加载阶段.

加载阶段

加载阶段就是: 从开始解析HTML文档,到页面绝大部分资源加载渲染完毕结束

大致分为以下流程:

  1. 构建DOM树

  2. 构建stylesheets

  3. 样式和布局

  4. 分层

  5. 描绘

  6. 图块化

  7. 栅格化

  8. 展示

下面我们也聚合为三个阶段谈一下:

1,2 之解析

从请求HTML文件到第一个块数据返回.浏览器就会在接受文档请求的同时去解析HTML.渲染引擎会将HTML进行词法分析,语法分析,最终将其转换为由DOM节点的父子关系组成的DOM树.

而在解析构建DOM树的同时,浏览器会去扫描整个html,去寻找高优先级的资源提前进行下载,如: 外链CSS/JS等资源,以及preload,prefetch等标签.所以当HTML解析到这些资源时,这些资源有可能已经下载完成,但也可能没有.

在解析的过程中如果遇到CSS,会将CSS转换成CSSOM树.CSSOM也就是我们常谈的stylesheets.也是从根节点遍历到叶子结点.和DOM树有着类似的结构.

这两者构建完进入下一阶段

3,4,5 之渲染

这一阶段是对基础树的一个转换,生成利于渲染使用的数据结构.

样式和布局计算

第三步叫做样式和布局.这一步是利用之前生成的DOM树和CSSOM树合成布局树(渲染树).

其中分为两部分,首先是根据DOM树和CSSOM树计算出实际需要展示的DOM节点,并生成布局树.布局树中会包含所有的可见节点以及这些节点对应的stylesheet.

所以在渲染树中像display: none的节点实际上是不会添加到渲染树中的,而visibiliy: hidden的节点会,因为它占用实际的页面空间.

布局树生成后,会进入实际的布局计算阶段.布局计算阶段会根据布局树以及视口大小实际计算出每个DOM节点的实际坐标和大小.也就是说会将width: 50%等相对属性值转换成实际px值.这个操作的首次触发叫做布局(layout),而之后的对DOM节点大小和位置的再计算就叫做重排(Reflow)

分层

这一步叫做分层.会将上一步拿到的布局树转换为图层树.

也就是说浏览器会以一定的规则将节点分为类似于PS中的图层,最后将多个图层合并在一起,显示在页面上.具体的分层信息,大家可以通过Devtools下的Layer面板查看页面的分层情况.

一般来说,并不是每个节点都会有自己的图层.因为绘制图层的操作开销较大,所以当节点不满足图层创建条件时会隶属于它的父节点的图层.一直追溯到最顶端的默认图层.

那为什么需要分层呢?我理解主要是因为:

  1. 绘制顺序

  2. 提升渲染效率

绘制顺序是指当页面上有多个块叠在一起展示时,如: 多个不同z-index的div,浏览器将这些不同z-index的块儿分层从底到高合成渲染.确保页面最后的精确展示

提升渲染效率是指当页面下次绘制时,可能需要将上一次绘制时的某一块进行旋转,缩放,改变阴影等操作.如果是这一块儿是单独的图层,只需要对该层进行相应操作,并和其它层进行层合成.就能直接渲染,不用再进行重排等重操作.

具体有哪些CSS可以产生层,可以参考这个文档:层叠上下文

而当最后分层树生成后就会进入下一步

描绘

在这一步中会通过之前生成的布局树和分层树,生成绘制指令列表.

绘制指令包含三部分:

  1. 行为.如画一个矩形等

  2. 位置和大小.

  3. 样式.

大家可以在Layer面板中的Paint Profile中看到

image.png

生成描绘指令后就会进入一下阶段,进行对页面的实际绘制和展示.

6,7,8 之 合成展示

这是渲染的最后一阶段.本阶段最终会将下一帧展示在我们的屏幕上.

图块化

这一步是利用之前生成的绘制指令和图层数据,生成一个个图块.

也就是说会将每个单独图层,依据视口位置和大小,将其分成一个个图块.

因为一个图层可能会非常长,远远超过视口的可展示范围.每次将整个图层绘制出来,会多很多无谓开销.所以将一个图层分为多个图块,进行分别处理.

浏览器渲染的图块儿和地图渲染时的图块概念类似.一个页面像一个由多个图块组成的拼图.

当生成图块后会进入下一步

栅格化

所谓栅格化是利用之前生成的图块生成位图,并将其存储在GPU内存中.

为了节省绘制开销,会优先将视口附近的图块生成为位图.通常情况下位图的生成是利用GPU去完成的.

位图是指由像素点构成的图像.由大量单个像素点组合而成.可以方便的由计算机处理绘制.

显示

这一步利用位图,在屏幕上完整展示合成后的帧.

当所有需要展示的图块被栅格化后,浏览器会被通知去绘制该位图.

最后展示在我们的显示器上

渲染结束

上面三个阶段,八个步骤统称为我们的渲染阶段.在我们每一帧之前都可能会重复执行.所以为了页面的渲染效率,我们需要尽可能的加快这些步骤的执行时间

最后

页面到这里可以说是半个生命周期已经完成了.

下一篇会讲讲我在项目中是如何思考和完成性能优化的.

参考

developer.mozilla.org/en-US/docs/…

datacadamia.com/web/browser…

developer.mozilla.org/en-US/docs/…

cabulous.medium.com/how-does-br…

developer.mozilla.org/en-US/docs/…

developers.google.com/web/fundame…

developers.google.com/web/updates…