前端性能优化指导原则
核心要素:
- 用户的使用环境
- 站点自身的性能表现
用户的使用环境我们是没办法控制的,我们只能优化自己。一般站在自身角度的优化主要分为两大类
- 网络性能
- 渲染性能
我们一个一个来讲:先说网络优化,主要包括三大类
- 减少关键资源个数
- 减少关键资源的RTT
- 降低关键资源的大小
首先我们先明确一下什么是关键资源:能阻塞网页首次渲染的资源称为关键资源。有一些比如图片,视频,音频 是不会阻塞渲染的,就不是关键资源。而html、css、js是会阻塞首次渲染的,因为这些会影响DOM树和CSSOM树的构建。
网络优化
减少关键资源个数
一般我们会做两件事情
- async/defer : 一般来说,如果你的js文件中不会影响DOM和CSSOM的生成,那么就可以增加async、defer这两个属性来将js文件变成非关键资源 让其异步加载,这样就不会阻塞首屏的渲染。
- async:当浏览器遇到 async 属性时,会异步加载脚本,不会阻塞 HTML 文档的解析过程。异步加载的脚本会在加载完成后立即执行,不会按照它们在 HTML 文档中的顺序执行。如果多个脚本都设置了 async 属性,它们的加载和执行顺序是不确定的。
- defer:当浏览器遇到 defer 属性时,会异步加载脚本,但会在 HTML 文档解析完毕后按照它们在 HTML 文档中的顺序执行。defer 属性保证了脚本的执行顺序,但是脚本的加载仍然是异步的,不会阻塞 HTML 文档的解析过程。
总结区别:
- async 属性用于异步加载脚本,不会阻塞 HTML 文档的解析,脚本加载和执行顺序不确定。
- defer 属性用于异步加载脚本,不会阻塞 HTML 文档的解析,脚本会按照它们在 HTML 文档中的顺序执行。
使用时的注意事项:
- 如果脚本之间有依赖关系,需要按照顺序执行,可以使用 defer 属性。
- 如果脚本之间没有依赖关系,可以独立加载和执行,可以使用 async 属性。
- 为了最佳的兼容性,建议在使用 async 和 defer 属性时,同时提供 fallback 方案,以确保在不支持这些属性的浏览器中也能正常加载和执行脚本。
- 内联:将css、js改成内联的写法,这个可用,但是一般在工作中用的不多。
减少关键资源的RTT
这个比较多,可以分为以下这几种
- 升级HTTP协议: 1.1升级2
-
使用HTTP缓存:增加强缓存和协商缓存的命中率
-
优化请求接口:做一些请求合并等操作
-
使用数据缓存:将不变的数据缓存下来
-
DNS预解析
DNS 预解析(DNS Prefetching)是一种浏览器技术,用于在用户点击链接之前提前解析域名对应的 IP 地址,以加快网页的加载速度。通过预先解析域名,浏览器可以在用户请求页面之前获取到所需的 IP 地址,从而减少 DNS 解析的时间。
DNS 预解析的工作原理如下:
- 当浏览器遇到带有 href 属性的链接标签a或带有 src 属性的脚本标签script时,会检查这些链接或脚本的域名。
- 浏览器会异步地进行 DNS 解析,获取域名对应的 IP 地址。
- 浏览器将解析得到的 IP 地址缓存起来,以备后续使用。
- 当用户点击链接或需要加载脚本时,浏览器可以直接使用缓存的 IP 地址,无需再进行 DNS 解析,从而加快页面的加载速度。
DNS 预解析可以提高网页的加载速度,特别是在存在大量外部资源(如图片、脚本、样式表等)的网页中。通过提前解析这些资源的域名,可以减少 DNS 解析的时间,从而加快网页的整体加载速度。
在 HTML 中,可以通过以下方式启用 DNS 预解析:
- 使用 标签的 rel 属性设置为 "dns-prefetch",并指定要预解析的域名。
<link rel="dns-prefetch" href="//example.com">
- 使用 HTTP 头部的 Link 字段来指定要预解析的域名。
Link: <//example.com>; rel=dns-prefetch
需要注意的是,DNS 预解析可能会增加网络流量和域名服务器的负载,因此在使用时需要谨慎评估其对网站性能的实际影响。
- 使用CDN
-
减少关键资源的大小和减少关键资源的大小搭配
减少关键资源的大小
这个一般分为这几步
- 代码分割
- 压缩
- 静态资源服务器开始GZip
- 非首屏元素延迟加载
这几个熟悉webpack配置的和nginx的都清楚,很好就可以解决。
渲染优化
先总结说一下
- 提高单帧的生成速度
- 合成线程->重绘->重排
- 避免频繁的垃圾回收
- 减少js的执行时间。
首先先简单唠叨以下浏览器的渲染流水线。
- 解析 HTML:浏览器首先解析 HTML 文档,构建 DOM(文档对象模型)树。DOM 树表示了网页的结构,包括标签、元素和它们之间的关系。
- 解析 CSS:浏览器解析 CSS 样式表,构建 CSSOM(CSS 对象模型)树。CSSOM 树表示了网页的样式信息,包括选择器、样式规则和它们之间的关系。
- 构建渲染树:浏览器将 DOM 树和 CSSOM 树结合起来,生成渲染树(Render Tree)。渲染树只包含需要显示的元素,它是一个包含可见节点的树结构。
- 布局计算:浏览器对渲染树进行布局计算,确定每个元素在屏幕上的位置和大小。这个过程称为布局(Layout)或回流(Reflow)。
- 绘制页面:浏览器使用渲染树和布局计算的结果,将页面内容绘制到屏幕上。这个过程称为绘制(Painting)或重绘(Repaint)。
- 合成和显示:浏览器将绘制的页面内容合成为图层,并将图层显示在屏幕上。图层合成是为了提高页面的性能和交互体验。
- JavaScript 解析和执行:浏览器在渲染过程中遇到 JavaScript 代码时,会解析并执行 JavaScript。JavaScript 可以修改 DOM 树、CSSOM 树和渲染树,从而影响页面的显示和交互。
渲染流水线是在渲染进程中完成的,而浏览器完成页面的渲染是需要渲染进程、网络进程、GPU进程合作完成的。
- 渲染进程(Renderer Process):渲染进程负责解析 HTML、CSS 和 JavaScript,构建 DOM 树、CSSOM 树和渲染树,进行布局计算、绘制和页面合成。渲染进程通过与网络进程和 GPU 进程的通信来获取页面资源和进行页面渲染。
- 网络进程(Network Process):网络进程负责处理网络请求和响应,包括获取 HTML、CSS、JavaScript、图片等资源。当渲染进程需要加载资源时,会发送请求给网络进程,网络进程负责获取资源并返回给渲染进程。
- GPU 进程(GPU Process):GPU 进程负责将渲染进程生成的页面内容进行合成和显示。GPU 进程使用硬件加速技术,将渲染树转换为图层,并将图层合成为最终的页面内容。GPU 进程与渲染进程之间通过共享内存进行通信,以提高页面渲染的性能和效果。
这三个进程之间的协作可以简单描述为以下几个步骤:
- 渲染进程解析 HTML、CSS 和 JavaScript,构建渲染树。
- 渲染进程向网络进程发送请求,获取页面所需的资源。
- 网络进程将资源返回给渲染进程。
- 渲染进程进行布局计算、绘制和页面合成,生成页面内容。
- 渲染进程将页面内容发送给 GPU 进程。
- GPU 进程将页面内容进行合成和显示,最终呈现在屏幕上。
通过将不同的任务分配给不同的进程,浏览器可以实现并行处理和硬件加速,提高页面的渲染性能和用户体验。同时,这种进程间的协作也增加了浏览器的安全性,防止恶意网页对系统的攻击。
返回来继续说,进过三个进程的循环协作,会形成一帧图像。而每秒会形成60帧,页面才比较顺滑,如果没帧的时间超过了16.67ms,就会导致页面出现卡顿和掉帧的现象。所以我们优化的主要目的就是加快每帧的渲染速度。
有的时间页面的渲染是css控制的,有的时候是js控制的,无论哪种,只要是在计算样式阶段有布局信息的修改,那就会触发重排操作,那这个代价是比较高的。如果只是修改颜色之类的操作,那么不会触发布局更新,只会触发样式的重绘,相对来讲代价小一点。还有一种情况是用过css来实现一些形变动画(tranform)等,在合成线程上执行,它不会触发重排和重绘,所以速度会非常快。
所以我们尽可能的处理: 合成线程 > 重绘 > 重排。常用的手段包括:使用合理使用css动画、DOM批处理、transform等。
另外js中用的是自动的垃圾回收机制,这是一种自动的内存管理机制,用于检测和回收不再使用的对象,以释放内存空间。垃圾回收器会定期扫描堆内存中的对象,标记那些仍然被引用的对象,并清除那些没有被引用的对象。
垃圾回收过程通常会在主线程中执行,因为 JavaScript 是单线程的语言,主线程负责执行 JavaScript 代码和处理用户交互。当垃圾回收器执行时,主线程可能会被阻塞,导致一些代码的执行延迟或页面的卡顿。
所以尽可能的少使用临时变量来减少js的垃圾回收。
从减少js的执行时间上来讲,少用js应该是不太能做到。只能尽可能减少js执行频率。当 JavaScript 代码执行时间较长时,主线程会被占用较长时间,导致其他任务(如布局计算、绘制等)无法及时执行,从而延迟页面的渲染和显示。
我们还是有一些手段可以来优化的,比如异步执行、合理的错误处理、按需加载、减少循环,充分了解使用的框架等。
另外可以将一些长耗时的任务移除主线程,比如放到worker中执行。
到此我们的性能优化从网络层面和渲染层面简单介绍了一下,也给出了一些可行性的方法。基本原理就这样,实际情况中还需要从实际业务出发,毕竟每个框架的优化手段不一样。。。
最后补充一下浏览器中的进程和线程。
在现代浏览器中,通常会包含以下进程和线程:
- 浏览器进程(Browser Process):
- 主线程(Main Thread):负责处理用户界面、JavaScript 执行、网络请求等。
- GPU 进程(GPU Process):负责处理图形渲染和硬件加速。
- 网络进程(Network Process):负责处理网络请求和响应。
- 存储进程(Storage Process):负责处理本地存储相关操作。
- 扩展进程(Extension Process):负责处理浏览器扩展相关操作。
- 渲染进程(Rendering Process):
- 主线程(Main Thread):负责构建页面的渲染树、进行布局计算和绘制。
- 合成线程(Compositor Thread):负责处理图层合成和页面显示,通过 GPU 进程进行硬件加速。
- 事件线程(Event Thread):负责处理用户输入事件。
- 工作线程(Worker Thread):负责执行 Web Worker 中的 JavaScript 代码。
- 插件进程(Plugin Process):负责处理浏览器插件相关操作。
文章评论