网络知识 娱乐 前端的性能优化总结

前端的性能优化总结

性能优化

一、RAIL性能模型

1. 什么是RAIL

  • R - Response响应: 并非网络请求的响应,而是Web应用给用户的响应体验;

  • A - Animation动画:动画是为了提高用户体验,但是添加的动画是否流畅;

  • I - Idle空闲:让浏览器(主线程)有足够的空闲时间处理用户的交互,而不是一直在繁忙状态;

    ​ 浏览器控制台有一个performance选项卡,列出了所有和性能相关的指标。

  • L - Load加载:网络加载

2. RAIL的目标

​ Rail的目标就是提高用户体验

3. RAIL评估标准

​ Google有庞大的用户群体,经常进行用户的问卷调查,得出了一些结论;

  • 响应:处理时间应在50ms内完成。从用户输入到获得响应应在100ms内完成,但是浏览器处理用户输入是需要50ms左右的,所以保守估计程序处理的时间应在50ms内最好;

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oAAzkT7c-1645851503347)(./imgs/image-20210623111056500.png)]

  • 动画:每10ms产生一帧。为了保证浏览器的60帧的帧率,则每一帧的平均时间为16.67ms,但是浏览器每一帧的绘制会消耗6ms左右的时间,因此程序产生一帧画面的时间保守应在10ms内最好;

  • 空闲:尽可能增加空闲时间。大量的运算和一些长时间占用计算资源的事情交给后端去做,前端需要留出足够的时间来处理用户的输入;

  • 加载:在5s内完成内容的加载并且可以交互。这个要求很高,因为用户可能使用pc、移动端等设备,可能处在网络环境较差的情况,这是无法避免的。

​ 这四个标准都是终极目标,同时满足这些的web应用堪称完美。

二、性能优化工具

1. Chrome Devtools

2. WebPageTest

  • 在线网站测试:webpagetest.org
  • 本地服务器,需要配置安装

3. Lighthouse

​ 它是Google完成的一个开源项目。它默认是在移动端平台测试网页性能。

  • 使用npm安装,然后使用lighthouse

    npm i -g lighthouse
    lighthouse https://www.bilibili.com
    
  • 查看本地报告

    在运行完命令之后会将测试报告存在本地:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-j9DZpxlW-1645851503349)(./imgs/image-20210623124514069.png)]

​ 直接打开这个网页就能看到测试结果。而且还能看到他给的优化提示,会提示你做什么事情能优化大概多长时间。

  • Chrome调试工具中集成了一个lighthouse。

三、常用的性能测量API

  • 关键时间节点(Navigation Timing, Resource Timing)
  • 网络状态(Network APIs)
  • 客户端服务端协商(HTTP Client Hints)& 网页显示状态(UI APIs)

四、渲染优化

1. 现代浏览器网页渲染原理

关键渲染路径(critical rendering path):从触发到渲染的步骤。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BV4WyfJ8-1645851503349)(./imgs/image-20210623134326029.png)]

  • 拿到DOM后,可能会有JavaScript来实现页面上的视觉变化,比如增加删除DOM元、用JS来实现动画。除了JavaScript这一步还可能有CSS的动画、Web Application API 等可以触发视图变动;
  • Style计算,第一部完成后,重新计算DOM元素的样式;
  • 布局,这是一个几何问题,计算各个元素在视图中的什么位置,以及绘制的大小如何;
  • 真正绘制元素,将文字、图片、颜色、阴影等绘制到视图上;
  • 合成,浏览器为了提高效率,将不同的元素绘制到不同的图层上,最终展示的是各个图层进行合成之后的结果。

​ 浏览器还有一些优化,那就是有的元素可以绕过一些关键渲染路径,比如有的元素不显示,或者不会影响布局,则在计算style包括布局绘制等可以绕过。

2. 重绘和回流

(1) 重绘

​ 布局layout关心的是元素的几何大小,即元素的位置和大小,那么更改他的颜色、阴影大小等不会影响布局,那么更改这些CSS的时候,浏览器会绕过layout阶段,直接到paint阶段,就是重绘。

(2) 回流

​ 更改一些CSS会影响到元素的布局,那么就需要进行回流,即渲染路径中必须要经过layout阶段。再次布局即为回流。

(3) 造成回流的操作

  • 添加、删除元素;
  • display : none, 这个和删除元素差不多;
  • 移动元素的位置:位置变了是要重新计算布局的;
  • 操作style可能造成回流;
  • 修改offsetLeft、scrollTop、clientWidth;
  • 修改浏览器的大小、字体的大小。

(4) 避免layout thrashing

  • 避免回流:尽量不要做哪些引起回流的操作;

  • 减少回流:像现在的很多前端框架,使用了虚拟DOM,将多次DOM样式或者DOM操作整理起来批量进行更新和删除,这样能有效地减少回流次数;

  • 读写分离:将读取元素属性和修改元素属性的操作分开,批量读取,再批量写入。可以使用FastDom库进行读写分离操作。

(5) 减少重绘的方案

  • 尽量使用transform而不直接去修改DOM的属性,可以不触发重绘和回流;

  • 创建新的图层。

3. 复合线程与图层

​ 复合图层(compositor thread) 图层(layers)

​ 复合:把页面拆解成不同的图层,有时当某一图层变化的时候,不影响其他图层的渲染,这样绘制的过程会更高效。

  • 复合线程在做什么:将页面拆分为图层进行绘制再进行复合。

    • 默认情况下,浏览器根据自己的规则进行拆分,主要的分析是元素和元素之间是否会相互影响,如果某一个元素会对其他元素造成过多的影响,则最好将其提取;
    • 也可以自己指定图层,z-index;
  • 利用DevTools了解网页的图层拆分情况;

  • 哪些样式仅影响复合,不触发重绘和回流:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nnmqTFOB-1645851503350)(./imgs/image-20210623160910715.png)]

​ 尽量使用transform,可以不触发重绘和回流。

利用GPU加速:

​ GPU 硬件加速是指应用 GPU 的图形性能对浏览器中的一些图形操作交给 GPU 来完成,因为 GPU 是专门为处理图形而设计,所以它在速度和能耗上更有效率。

​ GPU 加速通常包括以下几个部分:Canvas2D,布局合成, CSS3转换(transitions),CSS3 3D变换(transforms),WebGL和视频(video)。

​ 比如将2d transform转化为3d可以强制开启GPU加速,提高动画性能。

五、代码优化

1. JavaScript代码优化

  • JS的开销在哪里:加载、解析和编译、执行;

  • 引用JavaScript库的优化:

    • Code Splitting 代码拆分,按需加载;
    • Tree Shaking 代码减重
  • 减少主线程的工作量:

    • 避免长任务;
    • 避免超过1kb的行间脚本,因为行间脚本浏览器引擎是无法优化的;
    • 使用rAF和rIC进行时间调度——requestAnimationFrame、requestIdleCallback

2. v8引擎编译原理

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xXAPf9eg-1645851503350)(./imgs/image-20210623165344771.png)]

  • 源码 => AST => 字节码Bytecode => 机器码
  • 编译过程中会进行优化
  • 运行时可能会进行反优化

3. v8的代表性的优化机制

  • 脚本流:脚本在超过30kb的时候,认为脚本足够大,则新开一个线程对这个脚本进行解析,最后是多个解析结果的合并;

  • 字节码缓存:源码在翻译成字节码之后,如果有些字节码重复使用,则将其缓存;

  • 懒解析:针对函数而言,函数声明的时候不一定要立刻使用它,因此延迟对函数的解析直到使用之前。

    ​ 对于函数的懒解析lazy parsing,有时候我们的代码定义了函数想要立刻使用,即我们想要eager parsing饥饿解析,这样反而降低了效率,我们就需要手动对定义的函数最外层加一对小括号(),告诉浏览器这个函数要使用eager parsing。

    ​ 但是,项目往往会对JS代码进行压缩,在使用工具压缩的时候,往往会将这对()去掉,我们要使用一些工具将这对括号找回来,比如Optimize.js。

4. 对象优化

  • 对象属性初始化顺序尽量保持一致:JS是弱类型的语言,引擎在解析的时候,会根据自己的推断进行解析,JS的隐藏类型有21种,声明对象、初始化值的时候会创建隐藏类型,隐藏类型会根据顺序进行缓存,如果下次赋值有新的属性或者属性的赋值顺序发生变化,则缓存失效;

  • 对象实例化后尽量避免对其添加新的属性:实例化时已经存在的属性被称为In-Object属性,后添加的属性成为Normal/Fast属性,存储在property store里,需要通过描述数组间接查找,这样效率较低;

  • 尽量使用Array来代替Array-like对象:类数组对象虽然有索引,有length属性,但是它不是数组,没有forEach等方法。JS引擎对数组有性能优化而类数组对象没有。所以大量操作的时候可以将类数组对象转化为Array对象再操作。

  • 避免越界访问数组:越界的那个位置的值是undefined,数组也是对象,如果在当前位置找不到,则会沿着原型链想上找,这会造成额外的开销(差距为6倍)。此外还可能造成业务上的错误。

  • 避免元素类型转换:当数组中元素的类型相同的时候,JS引擎会给这个数组标记一个元素类型,并且有相应的优化,如果加入一个不同类型的元素,那么值钱的优化都作废,并且数组的类型会降级。数组越通用,优化越少。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vhfKyd9e-1645851503352)(./imgs/image-20210623185851063.png)]

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wQN4i1gW-1645851503352)(./imgs/image-20210623191308218.png)]

5. HTML优化

  • 减少iframe的使用:额外添加的页面加载会阻塞原文档的加载,也就是说onload必然发生在iframe加载完成之后。而且在一个页面中使用iframe加载另一个页面比直接渲染另一个页面的开销要大。如果一定要使用iframe则考虑在onload事件之后为其添加src属性,再加载iframe;

  • 压缩空白符:空白符虽然方便开发人员阅读,但是占用文档空间,所以在打包的时候最好去掉这些多余的空白符;

  • 避免节点的深层级嵌套:节点层级越深,生成的DOM树就越深,遍历起来就越复杂。

  • 避免使用table布局:使用起来也不灵活,开销也大;

  • 删除注释;

  • CSS & JavaScript尽量外链:写在行间的话会造成HTML文档过大,而且引擎不好做优化;

  • 删除元素的默认属性:也是一样,占多余空间;

  • 借助工具:html-minifier

6. CSS优化

  • 降低CSS对渲染的阻塞:尽量早的下载和解析CSS;
  • 减小的CSS文件的大小:按需加载CSS,一开始只加载用到的CSS文件,而没有用到的文件则延迟下载解析;
  • 利用GPU进行完成动画:利用关键渲染路径中的复合,不进行布局和重绘。单独将动画元素分成一个图层,GPU直接干预。
  • 使用CSS的contain属性:contain 属性的主要目的是隔离指定内容的样式、布局和渲染。开发人员可以使用这个 contain 属性来限制指定的DOM元素和它的子元素同页面上其它内容的联系;
  • 使用font-display属性:让文字尽早展示,且减轻文字闪动的问题。

六、资源的压缩合并

1. HTML资源压缩

  • 使用在线工具进行压缩:http://kangax.github.io/html-minifier/
  • 使用html-minifier等npm工具

2. CSS压缩

  • 使用在线工具
  • 使用clean-css等npm工具

3. JavaScript压缩与混淆

  • 在线工具:现在js比较复杂,不太现实;
  • Webpack对JS在构建时进行压缩。

4. CSS JS文件合并

  • 若干小文件:考虑将他们合并;
  • 文件和文件之间无冲突,服务相同的模块,可以合并;
  • 如果仅仅是为了优化加载速度,则最好不要合并:现在的应用要让用户尽快的看到页面的渲染,分开加载可以做到,如果是较大的文件,可能出现等待。另外,还要考虑缓存的问题,小文件粒度小,利于缓存。

七、图片优化

1. 图片格式

  • jpeg | jpg

    • 优点:很高的压缩比,而且画质和色彩还能较好的保存;

    • 使用场景:需要展示比较大的图片,还想保存画质效果的时候,比如轮播图;

    • 缺点:压缩比较高,如果想要展示清晰纹理和边缘的图片,会有锯齿感,比如logo

  • png

    • 优点:可以做透明背景的图片,还可以强调纹理和边缘,弥补了jpg的缺点;
    • 使用场景:贴图、小图片等;
    • 缺点:细节保存好所以体积较大,色彩上和jpg不相上下。
  • WebP:

    Google提出的一种新的图片格式。

    • 优点:jpg一样的大小,png一样的质量。

    • 兼容性:可能有10%的浏览器不兼容,慎用。

2. 图片加载优化

  • 懒加载

  • 渐进式图片

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZM5N7obg-1645851503353)(./imgs/image-20210624101754223.png)]

  • 响应式图片:根据不同的设备加载不同质量的图片,结合设备dpi和窗口实际大小。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UPEsxmz8-1645851503354)(./imgs/image-20210624102359993.png)]

八、字体优化

1. FOIT和FOUT

  • 字体为下载完成的时候,浏览器会对文字进行隐藏或对字体自动降级,导致字体闪烁;
  • Flash Of Invisible Text:字体加载完成之前先隐藏文字;
  • Flash Of Unstyled Text:字体加载完成之前先使用默认的已有的字体,加载完成后重新渲染文字。

2. font-display属性

​ @font-face中的属性

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-roujktj9-1645851503355)(./imgs/image-20210624105004046.png)]

​ 一共有五个值:auto、block、swap、fallback、optional,auto是让浏览器自动选择,没有意义。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6epreQyn-1645851503357)(./imgs/image-20210624104511126.png)]

3. 使用AJAX+Base64

  • 优点:解决了兼容性的问题,把字体用Base64在后台转码,然后前端通过异步请求的方式获得字体,这样字体的格式没有兼容性的问题。另外,使用异步加载,可以推迟字体的加载时间;
  • 缺点:由于字体通过Base64嵌入到资源文件(CSS文件中),那么就无法对字体进行缓存,字体的缓存就依赖于对CSS文件的缓存,本身不可控。

九、构建优化——Webpack