愚墨的博客
  • 首页
  • 前端技术
  • 面试
只争朝夕不负韶华
  1. 首页
  2. 前端技术
  3. 正文

谈一谈性能优化那点事-下篇

2021年08月02日 4286点热度 0人点赞 0条评论

前端性能优化指导原则

核心要素:

  • 用户的使用环境
  • 站点自身的性能表现

用户的使用环境我们是没办法控制的,我们只能优化自己。一般站在自身角度的优化主要分为两大类

  • 网络性能
  • 渲染性能

我们一个一个来讲:先说网络优化,主要包括三大类

  1. 减少关键资源个数
  2. 减少关键资源的RTT
  3. 降低关键资源的大小

首先我们先明确一下什么是关键资源:能阻塞网页首次渲染的资源称为关键资源。有一些比如图片,视频,音频 是不会阻塞渲染的,就不是关键资源。而html、css、js是会阻塞首次渲染的,因为这些会影响DOM树和CSSOM树的构建。

网络优化

减少关键资源个数

一般我们会做两件事情

  1. async/defer : 一般来说,如果你的js文件中不会影响DOM和CSSOM的生成,那么就可以增加async、defer这两个属性来将js文件变成非关键资源 让其异步加载,这样就不会阻塞首屏的渲染。
    1. async:当浏览器遇到 async 属性时,会异步加载脚本,不会阻塞 HTML 文档的解析过程。异步加载的脚本会在加载完成后立即执行,不会按照它们在 HTML 文档中的顺序执行。如果多个脚本都设置了 async 属性,它们的加载和执行顺序是不确定的。
    2. defer:当浏览器遇到 defer 属性时,会异步加载脚本,但会在 HTML 文档解析完毕后按照它们在 HTML 文档中的顺序执行。defer 属性保证了脚本的执行顺序,但是脚本的加载仍然是异步的,不会阻塞 HTML 文档的解析过程。

    总结区别:

    • async 属性用于异步加载脚本,不会阻塞 HTML 文档的解析,脚本加载和执行顺序不确定。
    • defer 属性用于异步加载脚本,不会阻塞 HTML 文档的解析,脚本会按照它们在 HTML 文档中的顺序执行。

    使用时的注意事项:

    • 如果脚本之间有依赖关系,需要按照顺序执行,可以使用 defer 属性。
    • 如果脚本之间没有依赖关系,可以独立加载和执行,可以使用 async 属性。
    • 为了最佳的兼容性,建议在使用 async 和 defer 属性时,同时提供 fallback 方案,以确保在不支持这些属性的浏览器中也能正常加载和执行脚本。
  2. 内联:将css、js改成内联的写法,这个可用,但是一般在工作中用的不多。

减少关键资源的RTT

这个比较多,可以分为以下这几种

  1. 升级HTTP协议: 1.1升级2

  2. 使用HTTP缓存:增加强缓存和协商缓存的命中率

  3. 优化请求接口:做一些请求合并等操作

  4. 使用数据缓存:将不变的数据缓存下来

  5. DNS预解析

    DNS 预解析(DNS Prefetching)是一种浏览器技术,用于在用户点击链接之前提前解析域名对应的 IP 地址,以加快网页的加载速度。通过预先解析域名,浏览器可以在用户请求页面之前获取到所需的 IP 地址,从而减少 DNS 解析的时间。

    DNS 预解析的工作原理如下:

    1. 当浏览器遇到带有 href 属性的链接标签a或带有 src 属性的脚本标签script时,会检查这些链接或脚本的域名。
    2. 浏览器会异步地进行 DNS 解析,获取域名对应的 IP 地址。
    3. 浏览器将解析得到的 IP 地址缓存起来,以备后续使用。
    4. 当用户点击链接或需要加载脚本时,浏览器可以直接使用缓存的 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 预解析可能会增加网络流量和域名服务器的负载,因此在使用时需要谨慎评估其对网站性能的实际影响。

  6. 使用CDN

  7. 减少关键资源的大小和减少关键资源的大小搭配

减少关键资源的大小

这个一般分为这几步

  1. 代码分割
  2. 压缩
  3. 静态资源服务器开始GZip
  4. 非首屏元素延迟加载

这几个熟悉webpack配置的和nginx的都清楚,很好就可以解决。

渲染优化

先总结说一下

  • 提高单帧的生成速度
    • 合成线程->重绘->重排
    • 避免频繁的垃圾回收
    • 减少js的执行时间。

首先先简单唠叨以下浏览器的渲染流水线。

  1. 解析 HTML:浏览器首先解析 HTML 文档,构建 DOM(文档对象模型)树。DOM 树表示了网页的结构,包括标签、元素和它们之间的关系。
  2. 解析 CSS:浏览器解析 CSS 样式表,构建 CSSOM(CSS 对象模型)树。CSSOM 树表示了网页的样式信息,包括选择器、样式规则和它们之间的关系。
  3. 构建渲染树:浏览器将 DOM 树和 CSSOM 树结合起来,生成渲染树(Render Tree)。渲染树只包含需要显示的元素,它是一个包含可见节点的树结构。
  4. 布局计算:浏览器对渲染树进行布局计算,确定每个元素在屏幕上的位置和大小。这个过程称为布局(Layout)或回流(Reflow)。
  5. 绘制页面:浏览器使用渲染树和布局计算的结果,将页面内容绘制到屏幕上。这个过程称为绘制(Painting)或重绘(Repaint)。
  6. 合成和显示:浏览器将绘制的页面内容合成为图层,并将图层显示在屏幕上。图层合成是为了提高页面的性能和交互体验。
  7. JavaScript 解析和执行:浏览器在渲染过程中遇到 JavaScript 代码时,会解析并执行 JavaScript。JavaScript 可以修改 DOM 树、CSSOM 树和渲染树,从而影响页面的显示和交互。

渲染流水线是在渲染进程中完成的,而浏览器完成页面的渲染是需要渲染进程、网络进程、GPU进程合作完成的。

  1. 渲染进程(Renderer Process):渲染进程负责解析 HTML、CSS 和 JavaScript,构建 DOM 树、CSSOM 树和渲染树,进行布局计算、绘制和页面合成。渲染进程通过与网络进程和 GPU 进程的通信来获取页面资源和进行页面渲染。
  2. 网络进程(Network Process):网络进程负责处理网络请求和响应,包括获取 HTML、CSS、JavaScript、图片等资源。当渲染进程需要加载资源时,会发送请求给网络进程,网络进程负责获取资源并返回给渲染进程。
  3. GPU 进程(GPU Process):GPU 进程负责将渲染进程生成的页面内容进行合成和显示。GPU 进程使用硬件加速技术,将渲染树转换为图层,并将图层合成为最终的页面内容。GPU 进程与渲染进程之间通过共享内存进行通信,以提高页面渲染的性能和效果。

这三个进程之间的协作可以简单描述为以下几个步骤:

  1. 渲染进程解析 HTML、CSS 和 JavaScript,构建渲染树。
  2. 渲染进程向网络进程发送请求,获取页面所需的资源。
  3. 网络进程将资源返回给渲染进程。
  4. 渲染进程进行布局计算、绘制和页面合成,生成页面内容。
  5. 渲染进程将页面内容发送给 GPU 进程。
  6. GPU 进程将页面内容进行合成和显示,最终呈现在屏幕上。

通过将不同的任务分配给不同的进程,浏览器可以实现并行处理和硬件加速,提高页面的渲染性能和用户体验。同时,这种进程间的协作也增加了浏览器的安全性,防止恶意网页对系统的攻击。

返回来继续说,进过三个进程的循环协作,会形成一帧图像。而每秒会形成60帧,页面才比较顺滑,如果没帧的时间超过了16.67ms,就会导致页面出现卡顿和掉帧的现象。所以我们优化的主要目的就是加快每帧的渲染速度。

有的时间页面的渲染是css控制的,有的时候是js控制的,无论哪种,只要是在计算样式阶段有布局信息的修改,那就会触发重排操作,那这个代价是比较高的。如果只是修改颜色之类的操作,那么不会触发布局更新,只会触发样式的重绘,相对来讲代价小一点。还有一种情况是用过css来实现一些形变动画(tranform)等,在合成线程上执行,它不会触发重排和重绘,所以速度会非常快。

所以我们尽可能的处理: 合成线程 > 重绘 > 重排。常用的手段包括:使用合理使用css动画、DOM批处理、transform等。

另外js中用的是自动的垃圾回收机制,这是一种自动的内存管理机制,用于检测和回收不再使用的对象,以释放内存空间。垃圾回收器会定期扫描堆内存中的对象,标记那些仍然被引用的对象,并清除那些没有被引用的对象。

垃圾回收过程通常会在主线程中执行,因为 JavaScript 是单线程的语言,主线程负责执行 JavaScript 代码和处理用户交互。当垃圾回收器执行时,主线程可能会被阻塞,导致一些代码的执行延迟或页面的卡顿。

所以尽可能的少使用临时变量来减少js的垃圾回收。

从减少js的执行时间上来讲,少用js应该是不太能做到。只能尽可能减少js执行频率。当 JavaScript 代码执行时间较长时,主线程会被占用较长时间,导致其他任务(如布局计算、绘制等)无法及时执行,从而延迟页面的渲染和显示。

我们还是有一些手段可以来优化的,比如异步执行、合理的错误处理、按需加载、减少循环,充分了解使用的框架等。

另外可以将一些长耗时的任务移除主线程,比如放到worker中执行。

到此我们的性能优化从网络层面和渲染层面简单介绍了一下,也给出了一些可行性的方法。基本原理就这样,实际情况中还需要从实际业务出发,毕竟每个框架的优化手段不一样。。。

最后补充一下浏览器中的进程和线程。

在现代浏览器中,通常会包含以下进程和线程:

  1. 浏览器进程(Browser Process):
    • 主线程(Main Thread):负责处理用户界面、JavaScript 执行、网络请求等。
    • GPU 进程(GPU Process):负责处理图形渲染和硬件加速。
    • 网络进程(Network Process):负责处理网络请求和响应。
    • 存储进程(Storage Process):负责处理本地存储相关操作。
    • 扩展进程(Extension Process):负责处理浏览器扩展相关操作。
  2. 渲染进程(Rendering Process):
    • 主线程(Main Thread):负责构建页面的渲染树、进行布局计算和绘制。
    • 合成线程(Compositor Thread):负责处理图层合成和页面显示,通过 GPU 进程进行硬件加速。
    • 事件线程(Event Thread):负责处理用户输入事件。
    • 工作线程(Worker Thread):负责执行 Web Worker 中的 JavaScript 代码。
  3. 插件进程(Plugin Process):负责处理浏览器插件相关操作。
标签: 暂无
最后更新:2023年07月23日

愚墨

保持饥渴的专注,追求最佳的品质

点赞
< 上一篇
下一篇 >

文章评论

取消回复

搜搜看看
历史遗迹
  • 2023年5月
  • 2022年9月
  • 2022年3月
  • 2022年2月
  • 2021年12月
  • 2021年8月
  • 2021年7月
  • 2021年5月
  • 2021年4月
  • 2021年2月
  • 2021年1月
  • 2020年12月
  • 2020年11月
  • 2020年9月
  • 2020年7月
  • 2020年5月
  • 2020年4月
  • 2020年3月
  • 2020年1月
  • 2019年5月
  • 2019年3月
  • 2019年2月
  • 2019年1月
  • 2018年9月
  • 2018年3月
  • 2018年2月
  • 2018年1月
  • 2017年11月
  • 2017年7月
  • 2017年6月
  • 2017年3月
  • 2017年2月
  • 2017年1月
  • 2016年12月
  • 2016年11月
  • 2016年9月
  • 2016年8月
  • 2016年7月
  • 2016年6月
  • 2016年5月
  • 2016年4月
  • 2016年3月
  • 2016年2月
  • 2016年1月
  • 2015年12月
  • 2015年10月
  • 2015年9月
  • 2015年7月
  • 2015年6月
  • 2015年4月

COPYRIGHT © 2020 愚墨的博客. ALL RIGHTS RESERVED.

THEME KRATOS MADE BY VTROIS