前端性能优化 笔记

什么是性能

从网站生成期那一刻到代码开始运行,消耗浏览器、服务器,所需要的一切资源统称性能

如何优化性能

浏览器的缓存策略

浏览器的缓存机制也就是我们说的HTTP缓存机制,其机制是根据HTTP报文的缓存标识进行的

缓存过程分析

  • 浏览器每次发起请求,都会先在浏览器缓存中查找该请求的结果以及缓存标识

  • 浏览器每次拿到返回的请求结果都会将该结果和缓存标识存入浏览器缓存中

WechatIMG716.jpeg

强制缓存

强制缓存就是向浏览器缓存查找该请求结果,并根据该结果的缓存规则来决定是否使用该缓存结果的过程,强制缓存的情况主要有三种(暂不分析协商缓存过程),如下:

  • 不存在该缓存结果和缓存标识,强制缓存失效,则直接向服务器发起请求
  • 存在该缓存结果和缓存标识,但该结果已失效,强制缓存失效,则使用协商缓存
  • 存在该缓存结果和缓存标识,且该结果尚未失效,强制缓存生效,直接返回该结果

结论:
当浏览器向服务器发起请求时,服务器会将缓存规则放入HTTP响应报文的HTTP头中和请求结果一起返回给浏览器,控制强制缓存的字段分别是Expires和Cache-Control,其中Cache-Control优先级比Expires高

WechatIMG717.jpeg

Expire

到期时间: 到了HTTP/1.1,Expire已经被Cache-Control替代,原因在于Expires控制缓存的原理是使用客户端的时间与服务端返回的时间做对比,那么如果客户端与服务端的时间因为某些原因(例如时区不同;客户端和服务端有一方的时间不准确)发生误差,那么强制缓存则会直接失效,这样的话强制缓存的存在则毫无意义

Cache-Control

在HTTP/1.1中,Cache-Control是最重要的规则,主要用于控制网页缓存,主要取值为:

  • public:所有内容都将被缓存(客户端和代理服务器都可缓存)
  • private:所有内容只有客户端可以缓存,Cache-Control的默认取值
  • no-cache:客户端缓存内容,但是是否使用缓存则需要经过协商缓存来验证决定
  • no-store:所有内容都不会被缓存,即不使用强制缓存,也不使用协商缓存
  • max-age=xxx (xxx is numeric):缓存内容将在xxx秒后失效

由上面的例子我们可以知道:

  • HTTP响应报文中expires的时间值,是一个绝对值

  • HTTP响应报文中Cache-Control为max-age=600,是相对值

WechatIMG718.jpeg

这里我们以博客的请求为例,状态码为灰色的请求则代表使用了强制缓存,请求对应的Size值则代表该缓存存放的位置,分别为from memory cache 和 from disk cache。
    
from memory cache代表使用内存中的缓存,from disk cache则代表使用的是硬盘中的缓存,浏览器读取缓存的顺序为memory –> disk。
复制代码

对于这个问题,我们需要了解内存缓存(from memory cache)和硬盘缓存(from disk cache),如下:

  • 内存缓存(from memory cache):内存缓存具有两个特点,分别是快速读取和时效性:

  • 快速读取:内存缓存会将编译解析后的文件,直接存入该进程的内存中,占据该进程一定的内存资源,以方便下次运行使用时的快速读取。

  • 时效性:一旦该进程关闭,则该进程的内存则会清空。

  • 硬盘缓存(from disk cache):硬盘缓存则是直接将缓存写入硬盘文件中,读取缓存需要对该缓存存放的硬盘文件进行I/O操作,然后重新解析该缓存内容,读取复杂,速度比内存缓存慢。

    在浏览器中,浏览器会在js和图片等文件解析执行后直接存入内存缓存中,那么当刷新页面时只需直接从内存缓存中读取(from memory cache);而css文件则会存入硬盘文件中,所以每次渲染页面都需要从硬盘读取缓存(from disk cache)

协商缓存

协商缓存就是强制缓存失效后,浏览器携带缓存标识向服务器发起请求,由服务器根据缓存标识决定是否使用缓存的过程,

  • 协商缓存生效,返回304
  • 协商缓存失效,返回200和请求结果结果,然后将请求结果和缓存标识存入浏览器缓存中

同样,协商缓存的标识也是在响应报文的HTTP头中和请求结果一起返回给浏览器的,控制协商缓存的字段分别有:Last-Modified / If-Modified-Since和Etag / If-None-Match,其中Etag / If-None-Match的优先级比Last-Modified / If-Modified-Since高。

总结

强制缓存优先于协商缓存进行,若强制缓存(Expires和Cache-Control)生效则直接使用缓存,若不生效则进行协商缓存(Last-Modified 上次请求返回的最后被修改时间 / If-Modified-Since和Etag / If-None-Match),协商缓存由服务器决定是否使用缓存,若协商缓存失效,那么代表该请求的缓存失效,重新获取请求结果,再存入浏览器缓存中;生效则返回304,继续使用缓存

Last-Modified / If-Modified-Since

服务器收到该请求,发现请求头含有If-Modified-Since字段,则会根据If-Modified-Since的字段值与该资源在服务器的最后被修改时间做对比,若服务器的资源最后被修改时间大于If-Modified-Since的字段值,则重新返回资源,状态码为200;否则则返回304,代表资源无更新,可继续使用缓存文件

Etag / If-None-Match

If-None-Match是客户端再次发起该请求时,携带上次请求返回的唯一标识Etag值,通过此字段值告诉服务器该资源上次请求返回的唯一标识值。服务器收到该请求后,发现该请求头中含有If-None-Match,则会根据If-None-Match的字段值与该资源在服务器的Etag值做对比,一致则返回304,代表资源无更新,继续使用缓存文件;不一致则重新返回资源文件,状态码为200

2、前端性能优化 – 用RAIL模型分析性能

以以下4个方面:
Response
Animation
Idle
Load

Response: 事件处理最好在50ms内完成

目标

  • 用户的输入到响应的时间不超过100ms,给用户的感受是瞬间就完成了。

优化方案

  • 事件处理函数在50ms内完成,考虑到idle task的情况,事件会排队,等待时间大概在50ms。适用于click,toggle,starting animations等,不适用于drag和scroll。
  • 复杂的js计算尽可能放在后台,如web worker,避免对用户输入造成阻塞
  • 超过50ms的响应,一定要提供反馈,比如倒计时,进度百分比等。
    idle task:除了要处理输入事件,浏览器还有其它任务要做,这些任务会占用部分时间,一般情况会花费50ms的时间,输入事件的响应则排在其后。
复制代码

Animation: 在10ms内产生一帧

目标

产生每一帧的时间不要超过10ms,为了保证浏览器60帧,每一帧的时间在16ms左右,但浏览器需要用6ms来渲染每一帧。
旨在视觉上的平滑。用户对帧率变化感知很敏感。

优化方案

  • 在一些高压点上,比如动画,不要去挑战cpu,尽可能地少做事,如:取offset,设置style等操作。尽可能地保证60帧的体验。
  • 在渲染性能上,针对不同的动画做一些特定优化

动画不只是UI的视觉效果,以下行为都属于

  • 视觉动画,如渐隐渐显,tweens,loading等
  • 滚动,包含弹性滚动,松开手指后,滚动会持续一段距离
  • 拖拽,缩放,经常伴随着用户行为

Idle: 最大化空闲时间

目标

  • 最大化空闲时间,以增大50ms内响应用户输入的几率

优化方案

  • 用空闲时间来完成一些延后的工作,如先加载页面可见的部分,然后利用空闲时间加载剩余部分,此处可以使用 requestIdleCallback API
  • 在空闲时间内执行的任务尽量控制在50ms以内,如果更长的话,会影响input handle的pending时间
  • 如果用户在空闲时间任务进行时进行交互,必须以此为最高优先级,并暂停空闲时间的任务

Load: 传输内容到页面可交互的时间不超过5秒

如果页面加载比较慢,用户的交点可能会离开。加载很快的页面,用户平均停留时间会变长,跳出率会更低,也就有更高的广告查看率

目标

  • 优化加载速度,可以根据设备、网络等条件。目前,比较好的一个方式是,让你的页面在一个中配的3G网络手机上打开时间不超过5秒
  • 对于第二次打开,尽量不超过2秒

优化方案

  • 在手机设备上测试加载性能,选用中配的3G网络(400kb/s,400ms RTT),可以使用 WebPageTest 来测试
  • 要注意的是,即使用户的网络是4G,但因为丢包或者网络波动,可能会比预期的更慢
  • 禁用渲染阻塞的资源,延后加载
  • 可以采用 lazy load,code-splitting 等 其他优化 手段,让第一次加载的资源更少

总结
RAIL是一个旅程,为了提升用户在网站的交互体验而不断探索。你需要去理解用户如何感知你的站点,这样才能设置最佳的性能目标

  • 聚焦用户
  • 100ms内响应用户的输入
  • 10ms内产生1帧,在滚动或者动画执行时
  • 最大化主线程的空闲时间
  • 5s内让网页变得可交互

3、重排和重绘

重绘不一定导致重排,但重排一定会导致重绘

重排

如下情况会发生重排

  • 页面初始渲染,这是开销最大的一次重排
  • 添加/删除可见的DOM元素
  • 改变元素位置
  • 改变元素尺寸,比如边距、填充、边框、宽度和高度等
  • 改变元素内容,比如文字数量,图片大小等
  • 改变元素字体大小
  • 改变浏览器窗口尺寸,比如resize事件发生时
  • 激活CSS伪类(例如::hover)
  • 设置 style 属性的值,因为通过设置style属性改变结点样式的话,每一次设置都会触发一次reflow
  • 查询某些属性或调用某些计算方法:offsetWidth、offsetHeight等,除此之外,当我们调用 getComputedStyle方法,或者IE里的 currentStyle 时,也会触发重排,原理是一样的,都为求一个“即时性”和“准确性”。

影响范围:

  • 全局范围:从根节点html开始对整个渲染树进行重新布局。
  • 局部范围:对渲染树的某部分或某一个渲染对象进行重新布局 (元素宽高写死不变,只是内部内容发生改变)

重绘

如下情况会发生重绘

  • color
  • text-decoration
  • outline-color
  • outline-width

重排优化建议

  • 尽可能在低层级的DOM节点上,而不是像上述全局范围的示例代码一样,如果你要改变p的样式,class就不要加在div上,通过父元素去影响子元素不好。
  • 不要使用 table 布局,可能很小的一个小改动会造成整个 table 的重新布局。那么在不得已使用table的场合,可以设置table-layout:auto;或者是table-layout:fixed这样可以让table一行一行的渲染,这种做法也是为了限制reflow的影响范围

1、样式集中修改,明智且可维护的做法是更改类名而不是修改样式

2 分离读写操作 当我们修改了元素的几何属性,导致浏览器触发重排或重绘时。它会把该操作放进渲染队列,等到队列中的操作到了一定的数量或者到了一定的时间间隔时,浏览器就会批量执行这些操作。

3、将DOM离线 不在当前的DOM树中做修改
display: none
一旦我们给元素设置 display:none 时(只有一次重排重绘),元素便不会再存在在渲染树中,相当于将其从页面上“拿掉”,我们之后的操作将不会触发重排和重绘,添加足够多的变更后,通过 display属性显示(另一次重排重绘)。通过这种方式即使大量变更也只触发两次重排。另外,visibility : hidden 的元素只对重绘有影响,不影响重排
通过 documentFragment 创建一个 dom 碎片,在它上面批量操作 dom,操作完成之后,再添加到文档中,这样只会触发一次重排。
复制节点,在副本上工作,然后替换它!

4、使用 position 或者 fixed 脱离文档流
使用绝对定位会使的该元素单独成为渲染树中 body 的一个子元素,重排开销比较小,不会对其它节点造成太多影响。当你在这些节点上放置这个元素时,一些其它在这个区域内的节点可能需要重绘,但是不需要重排

5、动画优化
可以把动画效果应用到 position属性为 absolute 或 fixed 的元素上,这样对其他元素影响较小。
动画效果还应牺牲一些平滑,来换取速度,这中间的度自己衡量: 比如实现一个动画,以1个像素为单位移动这样最平滑,但是Layout就会过于频繁,大量消耗CPU资源,如果以3个像素为单位移动则会好很多
启用GPU加速 GPU 硬件加速是指应用 GPU 的图形性能对浏览器中的一些图形操作交给 GPU 来完成,因为 GPU 是专门为处理图形而设计,所以它在速度和能耗上更有效率。
GPU 加速通常包括以下几个部分:Canvas2D,布局合成, CSS3转换(transitions),CSS3 3D变换(transforms),WebGL和视频(video)。

/*
  * 根据上面的结论
  * 将 2d transform 换成 3d
  * 就可以强制开启 GPU 加速
  * 提高动画性能
  */
  div {
    transform: translate3d(10px, 10px, 0);
  }
复制代码

小结:

  • 渲染的三个阶段 Layout,Paint,Composite Layers。 Layout:重排,又叫回流。 Paint:重绘,重排重绘这些步骤都是在 CPU 中发生的。 Compostite Layers:CPU 把生成的 BitMap(位图)传输到 GPU,渲染到屏幕。

  • CSS3 就是在 GPU 发生的:Transform Opacity。在 GPU 发生的属性比较高效。所以 CSS3 性能比较高。

4、白屏时间

一 、白屏时间重要性:

白屏时间指,用户在浏览器输入地址到用户看到界面这个过程所需要的时间用户感知到页面的速度越快,用户的体验就越好,减少用户的跳出,提升页面的留存率

二、白屏经历了怎样的过程:

1、浏览器会先对页面进行域名解析,获取到服务器的IP地址后,进而和服务器进行通信。
Tips: 通常在整个加载页面的过程中,浏览器会多次进行DNS Lookup,包括页面本身的域名查询以及在解析HTML页面时加载的JS、CSS、Image、Video等资源产生的域名查询。

2、建立TCP连接请求
浏览器和服务端TCP请求建立的过程,是基于TCP/IP,该协议由网络层的IP和传输层的TCP组成。IP是每一台互联网设备在互联网中的唯一地址。
TCP通过三次握手建立连接,并提供可靠的数据传输服务。

3. 服务端请求处理响应
在TCP连接建立后,Web服务器接受请求,开始进行处理,同时浏览器端开始等待服务器的处理响应。
Web服务器根据请求类型的不同,进行相应的处理。静态资源如图片、CSS文件、静态HTML直接进行响应;如其他注册的请求转发给相应的应用服务器,进行如数据处理、缓存中取数据,将数据按照约定好的格式响应给浏览器。
在大型应用中,通常为分布式服务架构,应用服务器的处理有可能经过很多个系统的中间件,最终获取到需要的数据

4. 客户端下载、解析、渲染显示页面
在服务器返回数据后,客户端浏览器接收数据,进行HTML下载、解析、渲染显示

a. 如果是Gzip包,则先解压为HTML

b. 解析HTML的头部代码,下载头部代码中的样式资源文件或脚本资源文件

c. 解析HTML代码和样式文件代码,构建HTML的DOM树以及与CSS相关的CSSOM树

d. 通过遍历DOM树和CSSOM树,浏览器依次计算每个节点的大小、坐标、颜色等样式,构造渲染树

e. 根据渲染树完成绘制过程

浏览器下载HTML后,首先解析头部代码,进行样式表下载,然后继续向下解析HTML代码,构建DOM树,同时进行样式下载。当DOM树构建完成后,立即开始构造CSSOM树。理想情况下,样式表下载速度够快,DOM树和CSSOM树进入一个并行的过程,当两棵树构建完毕,构建渲染树,然后进行绘制。

Tips:浏览器安全解析策略对解析HTML造成的影响:

  • 当解析HTML时遇到内联JS代码,会阻塞DOM树的构建
  • 特别悲惨的情况: 当CSS样式文件没有下载完成时,浏览器解析HTML遇到了内联JS代码,此时!!!根据浏览器的安全解析策略,浏览器暂停JS脚本执行,暂停HTML解析。直到CSS文件下载完成,完成CSSOM树构建,重新恢复原来的解析。

一定要合理放置JS代码!!!

三、白屏-性能优化

至此,我们已经了解了从浏览器在打开一个链接开始,到屏幕展示的过程-白屏时间的历程,那这对每个环节中发生的事情,我们可以有针对性的进行相关的优化。

1. DNS解析优化
针对DNS Lookup环节,我们可以针对性的进行DNS解析优化。

  • DNS缓存优化
  • DNS预加载策略
  • 稳定可靠的DNS服务器

2. TCP网络链路优化
针对网络链路的优化,好像除了花钱没有什么更好的方式!

3. 服务端处理优化
服务端的处理优化,是一个非常庞大的话题,会涉及到如Redis缓存、数据库存储优化或是系统内的各种中间件以及Gzip压缩等…

4. 浏览器下载、解析、渲染页面优化
根据浏览器对页面的下载、解析、渲染过程,可以考虑一下的优化处理:

  • 尽可能的精简HTML的代码和结构
  • 尽可能的优化CSS文件和结构
  • 一定要合理的放置JS代码,尽量不要使用内联的JS代码

5、大量图片加载优化

1、 优先加载首屏所需图片,完成之后再加载其他部分图片

2、图片裁剪

3、使用更优的图片格式 jpeg ,png ,gif、webp

  • 一方面是减少单位像素所需的字节数
  • 另一方面是减少一张图片总的像素个数

6、浏览器渲染流程&Composite(渲染层合并)简单总结

浏览器请求、加载、渲染大致过程:

  • DNS查询
  • TCP连接
  • HTTP请求即响应
  • 服务器响应
  • 浏览器渲染
  • 浏览器渲染线程接收到请求,加载并渲染网页大致过程
  • 解析HTML 建立 DOM tree
  • 解析 css 建立 css rules tree
  • DOM 和 CSS 合并后生成 render tree
  • 布局 render tree ,负责元素尺寸, 位置计算
  • 绘制 render tree ,绘制页面像素信息
  • 浏览器会将各层的信息发送给GPU(GPU进程:最多一个,用于3D绘制等),GPU会将各层合成(composite),显示在屏幕上

WechatIMG614.jpeg

layout: 有了Render Tree,浏览器已经能知道网页中有哪些节点、各个节点的CSS定义以及他们的从属关系,从而去计算出每个节点在屏幕中的位置。

painting: 按照算出来的规则,通过显卡,把内容画到屏幕上。

reflow(回流):当浏览器发现某个部分发生了点变化影响了布局,需要倒回去重新渲染,内行称这个回退的过程叫 reflow。reflow 会从 这个 root frame 开始递归往下,依次计算所有的结点几何尺寸和位置。reflow 几乎是无法避免的。现在界面上流行的一些效果,比如树状目录的折叠、展开(实质上是元素的显 示与隐藏)等,都将引起浏览器的 reflow。鼠标滑过、点击……只要这些行为引起了页面上某些元素的占位面积、定位方式、边距等属性的变化,都会引起它内部、周围甚至整个页面的重新渲 染。通常我们都无法预估浏览器到底会 reflow 哪一部分的代码,它们都彼此相互影响着。

repaint(重绘):改变某个元素的背景色、文字颜色、边框颜色等等不影响它周围或内部布局的属性时,屏幕的一部分要重画,但是元素的几何尺寸没有变。

合成层创建标准:

  • 3D 或透视变换(perspective transform) CSS 属性
  • 使用加速视频解码的 元素 拥有 3D
  • (WebGL) 上下文或加速的 2D 上下文的 元素
  • 混合插件(如 Flash)
  • 对自己的 opacity 做 CSS动画或使用一个动画变换的元素
  • 拥有加速 CSS 过滤器的元素
  • 元素有一个包含复合层的后代节点(换句话说,就是一个元素拥有一个子元素,该子元素在自己的层里)
  • 元素有一个z-index较低且包含一个复合层的兄弟元素(换句话说就是该元素在复合层上面渲染)

合成层优点

  • 合成层的位图,会交由 GPU 合成,比 CPU 处理要快
  • 当需要 repaint 时,只需要 repaint 本身,不会影响到其他的层
  • 对于 transform 和 opacity 效果,不会触发 layout 和 paint

1.JavaScript。一般来说,我们会使用 JavaScript 来实现一些视觉变化的效果。比如用 jQuery 的 animate 函数做一个动画、对一个数据集进行排序或者往页面里添加一些 DOM 元素等。当然,除了 JavaScript,还有其他一些常用方法也可以实现视觉变化效果,比如:CSS Animations、Transitions 和 Web Animation API。

2.样式计算。此过程是根据匹配选择器(例如 .headline 或 .nav > .nav__item)计算出哪些元素应用哪些 CSS 3. 规则的过程。从中知道规则之后,将应用规则并计算每个元素的最终样式。

3.布局。在知道对一个元素应用哪些规则之后,浏览器即可开始计算它要占据的空间大小及其在屏幕的位置。网页的布局模式意味着一个元素可能影响其他元素,例如 元素的宽度一般会影响其子元素的宽度以及树中各处的节点,因此对于浏览器来说,布局过程是经常发生的。

4.绘制。绘制是填充像素的过程。它涉及绘出文本、颜色、图像、边框和阴影,基本上包括元素的每个可视部分。绘制一般是在多个表面(通常称为层)上完成的。

5.合成。由于页面的各部分可能被绘制到多层,由此它们需要按正确顺序绘制到屏幕上,以便正确渲染页面。对于与另一元素重叠的元素来说,这点特别重要,因为一个错误可能使一个元素错误地出现在另一个元素的上层。

“生成布局”(flow)和”绘制”(paint)这两步,合称为”渲染”(render)。重新渲染就是需要重新生成布局和重新绘制。 有上述的渲染流水线我们可以得知重绘不一定需要重排,重排必然导致重绘

重排和重绘会不断触发,这是不可避免的。但是,它们非常耗费资源,是导致网页性能低下的根本原因。 提高网页性能,就是要降低”重排”和”重绘”的频率和成本,尽量少触发重新渲染。

重排还重绘会消耗大量的CPU和GPU资源,前端新能优化最主要的优化点就是尽量减少重绘和重排。

总结合成层的优势:一般一个元素开启硬件加速后会变成合成层,可以独立于普通文档流中,改动后可以避免整个页面重绘,提升性能。但是通常情况下开启硬件加速会提高动画的流畅性,但是过多的合成层也会造成性能瓶颈,过多的合成层会占用大量的内存空间,如果一个页面中合成层太多,也会导致层爆炸

一些JavaScript方法可以调节重新渲染,大幅提高网页性能。
其中最重要的,就是 window.requestAnimationFrame() 方法。它可以将某些代码放到下一次重新渲染时执行。

window.requestAnimationFrame(fn);
复制代码

**window.requestIdleCallback()**也可以用来调节重新渲染。它指定只有当一帧的末尾有空闲时间,才会执行回调函数。只有当前帧的运行时间小于16.66ms时,函数fn才会执行。否则,就推迟到下一帧,如果下一帧也没有空闲时间,就推迟到下下一帧,以此类推

 requestIdleCallback(fn);
复制代码

它还可以接受第二个参数,表示指定的毫秒数。如果在指定 的这段时间之内,每一帧都没有空闲时间,那么函数fn将会强制执行。

requestIdleCallback(fn, 5000);
复制代码

7、前端浏览器动画性能优化

先总结几个要点

1、精简DOM,合理布局

2、使用transform代替left、top减少使用引起页面重排的属性

3、开启硬件加速

4、尽量避免浏览器创建不必要的图形层

5、尽量减少js动画,如需要,使用对性能友好的requestAnimationFrame

6、使用chrome performance工具调试动画性能

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享