一个页面的半生
楔子
作为前端方向的我们,对页面的性能优化是我们看家本领之一.
页面也是我们日常打交道非常多的一部分.所以在做性能优化前,对页面的生命周期有一个全面的了解是非常有必要的.
本篇作为性能优化系列之第一篇也是出于这个目的.
零
页面的生命周期从大方向上来看分为三个阶段:
-
导航阶段
-
加载渲染阶段
-
运行阶段
而今个儿是页面的半生,同时我们平常所称的性能优化大部分情况也是指优化页面加载,所以本篇只谈前两个阶段.
(注: 为了避免引入更高复杂度,不细述浏览器多进程模型各个进程的具体分工.有兴趣的朋友可以自己搜索,或者等我填坑 :) )
导航阶段
导航阶段顾名思义也就是: 从输入URL到页面开始解析HTML文档解析.
具体过程如下:
-
从用户在地址栏输入开始
-
浏览器接收到输入开始解析内容.如果是网页地址则进入下一步
-
查找资源缓存,如果有直接返回.没有进入下一步
-
根据输入进行DNS的解析,找到对应的服务器ip.
-
和服务器ip地址建立TCP链接
-
发送HTTP/HTTPS请求.
-
接收到返回解析数据.如果正常收到HTML文档.则进入下一步
-
提交渲染.
-
导航阶段结束
这里我们合成为三个阶段谈下:
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文档,到页面绝大部分资源加载渲染完毕结束
大致分为以下流程:
-
构建DOM树
-
构建stylesheets
-
样式和布局
-
分层
-
描绘
-
图块化
-
栅格化
-
展示
下面我们也聚合为三个阶段谈一下:
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面板查看页面的分层情况.
一般来说,并不是每个节点都会有自己的图层.因为绘制图层的操作开销较大,所以当节点不满足图层创建条件时会隶属于它的父节点的图层.一直追溯到最顶端的默认图层.
那为什么需要分层呢?我理解主要是因为:
-
绘制顺序
-
提升渲染效率
绘制顺序是指当页面上有多个块叠在一起展示时,如: 多个不同z-index的div,浏览器将这些不同z-index的块儿分层从底到高合成渲染.确保页面最后的精确展示
提升渲染效率是指当页面下次绘制时,可能需要将上一次绘制时的某一块进行旋转,缩放,改变阴影等操作.如果是这一块儿是单独的图层,只需要对该层进行相应操作,并和其它层进行层合成.就能直接渲染,不用再进行重排等重操作.
具体有哪些CSS可以产生层,可以参考这个文档:层叠上下文
而当最后分层树生成后就会进入下一步
描绘
在这一步中会通过之前生成的布局树和分层树,生成绘制指令列表.
绘制指令包含三部分:
-
行为.如画一个矩形等
-
位置和大小.
-
样式.
大家可以在Layer面板中的Paint Profile中看到
生成描绘指令后就会进入一下阶段,进行对页面的实际绘制和展示.
6,7,8 之 合成展示
这是渲染的最后一阶段.本阶段最终会将下一帧展示在我们的屏幕上.
图块化
这一步是利用之前生成的绘制指令和图层数据,生成一个个图块.
也就是说会将每个单独图层,依据视口位置和大小,将其分成一个个图块.
因为一个图层可能会非常长,远远超过视口的可展示范围.每次将整个图层绘制出来,会多很多无谓开销.所以将一个图层分为多个图块,进行分别处理.
浏览器渲染的图块儿和地图渲染时的图块概念类似.一个页面像一个由多个图块组成的拼图.
当生成图块后会进入下一步
栅格化
所谓栅格化是利用之前生成的图块生成位图,并将其存储在GPU内存中.
为了节省绘制开销,会优先将视口附近的图块生成为位图.通常情况下位图的生成是利用GPU去完成的.
位图是指由像素点构成的图像.由大量单个像素点组合而成.可以方便的由计算机处理绘制.
显示
这一步利用位图,在屏幕上完整展示合成后的帧.
当所有需要展示的图块被栅格化后,浏览器会被通知去绘制该位图.
最后展示在我们的显示器上
渲染结束
上面三个阶段,八个步骤统称为我们的渲染阶段.在我们每一帧之前都可能会重复执行.所以为了页面的渲染效率,我们需要尽可能的加快这些步骤的执行时间
最后
页面到这里可以说是半个生命周期已经完成了.
下一篇会讲讲我在项目中是如何思考和完成性能优化的.
参考
developer.mozilla.org/en-US/docs/…
developer.mozilla.org/en-US/docs/…
cabulous.medium.com/how-does-br…
developer.mozilla.org/en-US/docs/…