jtwang7 / JavaScript-Note

JavaScript学习笔记
10 stars 2 forks source link

前端性能优化 #58

Open jtwang7 opened 2 years ago

jtwang7 commented 2 years ago

前端性能优化

参考文章:

各阶段优化操作

- DNS解析阶段
  利用 DNS prefetch 技术,对特定域名进行预解析,浏览器会在页面加载完毕后进行该过程。
  <link ref="dns-prefetch" href="">

- 客户端发送HTTP请求阶段
  1. 减少请求次数:
    - 资源合并【Webpack打包技术】
    - 利用浏览器缓存【协商缓存;强缓存】
  2. 缩小请求体积:
    - 资源(图片/代码/...)压缩【gzip压缩;webpack压缩】
    - 图片优化【CSS3代替图片;webp格式;适当降低图片质量】
  3. 使用 HTTP2.0:
    - 二进制传输
    - 头部压缩
    - 多路复用
    - 服务端推送

- 服务端发送HTTP响应阶段
  1. 减少响应时间:
    - 静态资源使用 CDN
  2. 降低页面初始渲染时间:
    - SSR 服务器渲染

- 页面渲染阶段
  1. 减少阻塞:
    - 避免JS、CSS的阻塞
  2. 减少渲染次数:
    - 避免回流与重绘
  3. 减少渲染节点的数量:
    - (图片/DOM) 懒加载
  4. 提高渲染效率:
    - 减少DOM节点的操作【防抖;节流】
    - 利用事件循环及异步更新

导航

  1. 减少 HTTP 请求
  2. 使用 HTTP2
  3. 使用服务端渲染
  4. 静态资源使用 CDN
  5. 将 CSS 放在文件头部,JavaScript 文件放在底部
  6. 善用缓存,不重复加载相同的资源
  7. 压缩文件
  8. 图片优化
  9. 通过 webpack 按需加载代码,提取第三库代码,减少 ES6 转为 ES5 的冗余代码
  10. 减少重绘重排
  11. 使用事件委托
  12. 使用 transform 和 opacity 属性更改来实现动画

内容

减少 HTTP 请求

一个完整的 HTTP 请求需要经历 DNS 查找,TCP 握手,浏览器发出 HTTP 请求,服务器接收请求,服务器处理请求并发回响应,浏览器接收响应等过程。请求中每个环节都会占用一定的时间,而真正下载数据的时间在其中仅占用一小部分。因此建议将多个小文件合并为一个大文件,减少 HTTP 请求次数的原因,避免在建立请求过程中消耗过多的时间。


使用 HTTP2

HTTP2.0 相较于 HTTP1.1 而言,引入了新特性,加快了建立连接和传输数据的效率:

  1. 二进制传输
  2. 报文头部压缩
  3. 多路复用
  4. 服务端推送
  5. 加密传输

使用服务端渲染

客户端渲染: 获取 HTML 文件,根据需要下载 JavaScript 文件,运行文件,生成 DOM,再渲染。 服务端渲染:服务端返回 HTML 文件,客户端只需解析 HTML。


静态资源使用 CDN

内容分发网络(CDN)是一组分布在多个不同地理位置的 Web 服务器。当服务器离用户越远时,延迟越高。CDN 就是为了解决这一问题,在多个位置部署服务器,让用户离服务器更近,从而缩短请求时间。

CDN 原理

当用户访问一个网站时,如果没有 CDN,过程是这样的:

  1. 浏览器要将域名解析为 IP 地址,所以需要向本地 DNS 发出请求。
  2. 本地 DNS 依次向根服务器、顶级域名服务器、权限服务器发出请求,得到网站服务器的 IP 地址。
  3. 本地 DNS 将 IP 地址发回给浏览器,浏览器向网站服务器 IP 地址发出请求并得到资源。

如果用户访问的网站部署了 CDN,过程是这样的:

  1. 浏览器要将域名解析为 IP 地址,所以需要向本地 DNS 发出请求。
  2. 本地 DNS 依次向根服务器、顶级域名服务器、权限服务器发出请求,得到全局负载均衡系统(GSLB)的 IP 地址。
  3. 本地 DNS 再向 GSLB 发出请求,GSLB 的主要功能是根据本地 DNS 的 IP 地址判断用户的位置,筛选出距离用户较近的本地负载均衡系统(SLB),并将该 SLB 的 IP 地址作为结果返回给本地 DNS。
  4. 本地 DNS 将 SLB 的 IP 地址发回给浏览器,浏览器向 SLB 发出请求。
  5. SLB 根据浏览器请求的资源和地址,选出最优的缓存服务器发回给浏览器。
  6. 浏览器再根据 SLB 发回的地址重定向到缓存服务器。
  7. 如果缓存服务器有浏览器需要的资源,就将资源发回给浏览器。如果没有,就向源服务器请求资源,再发给浏览器并缓存在本地。

将 CSS 放在文件头部,JavaScript 文件放在底部

JS 放在底部的原因: JS 加载和执行会阻塞 HTML 解析,浏览器碰到 JS 会先下载和处理 JS 文件,停止之前的 HTML 解析直到 JS 执行结束。 若 JS 中还存在 CSS 操作,则会先等待 CSS 文件全部加载完成,再执行 JS 代码,最后恢复 HTML 构建。

JS 文件也不是不可以放在头部,只要给 script 标签加上 defer 属性就可以了,异步下载,延迟执行。

CSS 放在头部的原因: 先加载 HTML 再加载 CSS,会让用户第一时间看到的页面是没有样式的、“丑陋”的。为了避免这种情况发生,就要将 CSS 文件放在头部了。


善用缓存,不重复加载相同的资源

参考:浏览器缓存 为了避免用户每次访问网站都得请求文件,浏览器提供了强缓存、协商缓存等手段。 强缓存:Expires(绝对时间)Cache-Control: max-age=xxx(相对时间) 协商缓存:Last-Modified(服->客) / If-Modified-Since(客->服)Etag(服->客) / If-None-Match(客->服)

采用缓存策略,会产生一个问题:当文件更新了怎么办?怎么通知浏览器重新请求文件? 可以通过更新页面中引用的资源链接地址,让浏览器主动放弃缓存,加载新资源。 具体做法: 把资源地址 URL 的修改与文件内容关联起来,也就是说,只有文件内容变化,才会导致相应 URL 的变更,从而实现文件级别的精确缓存控制。什么东西与文件内容相关呢?我们会很自然的联想到利用数据摘要要算法对文件求摘要信息,摘要信息与文件内容一一对应,就有了一种可以精确到单个文件粒度的缓存控制依据了。


压缩文件

压缩文件可以减少文件下载时间,让用户体验性更好。 在 webpack 中可以使用如下插件进行压缩:

使用 gzip 压缩: 可以通过向 HTTP 请求头中的 Accept-Encoding 头添加 gzip 标识来开启这一功能。当然,服务器也得支持这一功能。


图片优化

  1. 图片懒加载 在页面中,先不给图片设置路径,只有当图片出现在浏览器的可视区域时,才去加载真正的图片。对于图片很多的网站来说,一次性加载全部图片,会对用户体验造成很大的影响,所以需要使用图片延迟加载。
  2. 响应式图片 响应式图片的优点是浏览器能够根据屏幕大小自动加载合适的图片。
  3. 缩略图 用缩略图的方式展示给用户,并且当用户鼠标悬停在上面时才展示全图。如果用户从未真正将鼠标悬停在缩略图上,则浪费了下载图片的时间。我们可以用两张图片来实行优化。一开始,只加载缩略图,当用户悬停在图片上时,才加载大图。还有一种办法,即对大图进行延迟加载,在所有元素都加载完成后手动更改大图的 src 进行下载。
  4. 降低图片质量
  5. 尽可能利用 CSS3 效果代替图片 有很多图片使用 CSS 效果(渐变、阴影等)就能画出来,这种情况选择 CSS3 效果更好。因为代码大小通常是图片大小的几分之一甚至几十分之一。
  6. 使用 webp 格式的图片 WebP 的优势体现在它具有更优的图像数据压缩算法,能带来更小的图片体积,而且拥有肉眼识别无差异的图像质量;同时具备了无损和有损的压缩模式、Alpha 透明以及动画的特性,在 JPEG 和 PNG 上的转化效果都相当优秀、稳定和统一。

通过 webpack 按需加载代码,提取第三库代码,减少 ES6 转为 ES5 的冗余代码

懒加载或者按需加载,是一种很好的优化网页或应用的方式。这种方式实际上是先把你的代码在一些逻辑断点处分离开,然后在一些代码块中完成某些操作后,立即引用或即将引用另外一些新的代码块。这样加快了应用的初始加载速度,减轻了它的总体体积,因为某些代码块可能永远不会被加载。


减少重绘重排

参考:my-issue:JS深入浅出 - 浏览器的重绘(Repaint)与回流(Reflow) 浏览器的优化机制: 由于每次回流/重绘都会造成额外的计算消耗,因此大多数浏览器都会通过队列化修改并批量执行来优化重排过程。浏览器会维护1个队列,把所有会引起回流、重绘的操作放入这个队列。等队列中的操作到了一定的数量或者到了一定的时间间隔(取决于浏览器的重绘频率,一般近似于显示器的刷新频率60Hz),浏览器就会 flush 清空队列,进行一个批处理。上述优化机制能够让多次的回流、重绘变成一次回流重绘。 但是,某些操作会强制浏览器立即清空回调队列中的回流/重绘操作:

clientWidth、clientHeight、clientTop、clientLeft
offsetWidth、offsetHeight、offsetTop、offsetLeft
scrollWidth、scrollHeight、scrollTop、scrollLeft
scrollIntoView()、scrollIntoViewIfNeeded()
getComputedStyle()
getBoundingClientRect()
scrollTo()

浏览器为了给你最精确的值,需要flush队列,因为队列中可能会有影响到这些值的操作。即使你获取元素的布局和样式信息跟最近发生或改变的布局信息无关,浏览器都会强行刷新渲染队列。引擎会重新渲染来确保获取的值是实时的。

减少回流和重绘

CSS

JavaScript

总结:减少对 render tree 的操作(合并多次多DOM和样式的修改),并减少对一些 style 信息的请求,尽量利用好浏览器的优化策略。


使用事件委托

事件委托利用了事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。

  1. 将事件提升到上层,可以保证页面渲染出可点击节点时,无延迟的起作用,不需要等待可点击节点添加事件处理程序 (因为事件处理程序在上层已经被绑定好了)
  2. 节省花在设置页面事件处理程序上的时间,多个重复的事件绑定会产生额外的遍历开销。
  3. 减少整个页面所需的内存

使用 transform 和 opacity 属性更改来实现动画

在 CSS 中,transforms 和 opacity 这两个属性更改不会触发重排与重绘,它们是可以由合成器(composite)单独处理的属性。 因此我们可以利用这两个属性,结合 transition 等动画 API 实现过渡效果等。