前端性能优化指南

235 阅读8分钟

性能指标和性能检测:前端性能检测指南 - 掘金 (juejin.cn)

1. 资源加载优化

1.1 资源大小方面优化

资源大小方面,主要通过压缩减少体积,删除无用代码减少代码体积。

借助 webpack 的 pligin 等

  • TerserWebpackPlugin

webpack5 内置的 plugin , 可以对 JS 进行丑化和压缩

TerserWebpackPlugin | webpack 中文文档

  • CssMinimizerPlugin

对 CSS 进行压缩

CssMinimizerWebpackPlugin | webpack 中文文档

  • 开启 GZIP

通过 webpack 开启 GZIP 可以对资源进行格式压缩。

CompressionWebpackPlugin | webpack 中文文档

  • html-webpack-plugin

在生成环境默认会对 HTML 进行压缩。 也可以修改配置

  • Tree-shaking

Webpack 在生成环境下默认是开启的。可以删除一些无用的代码。

  • 图片压缩 ,或使用 WEBP

1.2 资源数量方面

在资源数量上,有些地方可以合并请求,少数请求解决多个资源的问题。有些地方需要分多个请求,加快资源下载速度

减少资源数量

  • 雪碧图

  • 将一些小的图片转成 base64 , svg 。 内嵌到 HTML 中。

  • SSR ,服务端渲染

增加资源数量

  • SplitChunksPlugin

进行分包的好处其实也可以帮助减少包的总体积。它可以把一些共享的包单独打包出来。

第二就是可以把大的包分成小的多个包。

浏览器是支持多个连接下载资源的。因此将一个大的包分成多个小的包。可以加快资源下载的速度。

1.3 资源位置方面

  • 使用 CND

CND 可以把静态资源缓存到 CDN 服务器。从距离和速度上加快资源的访问

  • 使用 service worker

使用 service worker 将资源缓存到用户本地

  • 资源划分到子域名

浏览器可以对一个域名建立多个连接。不过是有限制的。一般 6个。

而将资源分到子域名,则可以增加并行请求的数量

  • 避免重定向

重定向会多一个请求的往返

1.4 请求时机

  • Script 标签后置 ,或者增加 async 或 defer 属性

Script 的下载会堵塞 HTML 的解析,下载完执行也会堵塞 HTML 的解析,那么就可能导致 script 标签后的资源无法及时的请求

  • 懒加载和预加载

懒加载可以减少并发的请求数量。预加载可以利用空闲时间进行加载

  • DNS 预查询和 TCP 预连接

dns-prefetch 可以进行 NDS 预查询。在真正请求资源时,减少查询的时间。preconnect 可以进行预连接

不过查询的域名和文档的域名需要不一样,也就是文档需要有使用到第三方服务器的资源才有用。

<link rel="preconnect" href="https://abc.com" crossorigin>
<link rel="dns-prefetch" href="https://abc.com">
  • 资源预加载

Rel = preload 可以预加载资源 。

  <link rel="preload" href="style.css" as="style" />
  <link rel="preload" href="main.js" as="script" />

1.5 使用 http2

http2 有多路复用的特性。一定程度上解决了队头堵塞的问题。多个请求并行使用一个 TCP 连接。可以加快资源的加载

2. 网页优化

2.1 首屏加载

首屏加载可参考上面的资源加载。资源加载的优化可加快首屏文档,样式表等的加载。

此外还有其他方法可以加速首屏的加载!

  • CSS 放在 head 内进行加载

HTML 是可以边解析边渲染的。如果 CSS 放在末尾,就会先渲染没有样式的DOM 。 而如果是先加载 CSS , 只有 CSSDOM 解析完成,之后 HTML 才可以边解析边渲染。那么就可以一次性渲染出有样式的页面。

  • Script 放在末尾 , 或者增加 defer 或 async

script 的加载和执行都会堵塞 HTML的解析和渲染。

  • 减少使用 iframe 或者异步添加

Iframe 于主页面共享共享连接池 。 占用部分的连接

  • 使用 SSR

服务端渲染可以减少部分请求。并且服务器渲染时从自己身上请求资源更快

  • 使用骨架屏

降低或消去用户对屏幕空白的感知 。起码让用户知道在加载中,而不是打不开

  • 避免重定向

首页重定向并不明智,增加了一个请求往返。

  • 修改首页内容

首页如果有一些大的图片,那么加载的时间可能就会增加很多。合理使用 CSS 。

降低首页渲染的复杂度。当首页基本完成后

  • Service worker

Service worker 可以缓存一些资源。在用户再次打开时,可以快速的从磁盘中读取。

  • HTML 内容优化

HTML有些内容,如果暂时不显示,可以通过 JS 动态插入。因为即使不显示 ,也会被解析。

  • CSS 内容优化

使用 CSS3的一些特性,可以开启 GPU 加速渲染

另外是避免重复的叠加类目,标签名。因为 CSSDOM 的解析是递归解析的。即使目前 CSS 解析很快

2.2 渲染优化

合理利用 GPU 加速渲染

页面的渲染大部分还是使用 CPU 的。 CSS3 中一些属性,可以使页面分层交给 CPU 进行渲染。从而加速渲染过程

  • 使用 CSS 动画或者静态图片代替 GIF (GIF 比较消耗 CPU)
  • 使用 CSS 动画代替 JS 动画

JS 引擎和渲染引擎是不同的。JS 操作 DOM 的需要昂贵的代价

  • 利用 CSS3的一些属性,开启 GPU 渲染

比如以下这些:

    • Transform

    • Filter

    • Will-change

    • Opacity

减少回流

DOM 操作很昂贵 。特别是可能触发回流的 DOM操作。会使页面变得卡顿,消耗更多的 CPU

常见引起回流的情况:

  1. 添加或删除 DOM 节点

  2. ** 修改 DOM 节点的位置和大小**

  3. 游览器的大小发生改变

  4. 读取 width,getComputedStyle ,getBoundingClientRect 等需要计算的属性

针对这些情况,我们需要有一些相应的措施,去减少这些情况的发生:

  1. visibility:hidden 或者 opacity:0 替换 display: none 。

Hidden 是隐藏,但还在,而display:none 会删除节点,可能导致其他节点的位置变化!导致回流!

  1. 一次性批量处理节点

频繁的删除,添加节点,会都在页面变得很卡顿!因此要么减少次数,要么一次批量的添加或删除!

  • 一次性删除

比如把可能频繁删除阶段列表放在一个盒子中 ,那么删除时删除列表的父节点即可。

  • 使用 innerHTML

当你需要拼接比较复杂和内容和节点比较多的节点时,可以使用 字符串 。然后通过 innerHTML 添加到页面中!

需要注意 XSS 攻击!

  • 使用文档片段 (document fragment)

Document.createDocumentFragment() - Web API 接口参考 | MDN

文档片段可以理解为一个节点,但这个节点插入页面后,最后是不会被渲染出来的!

相当于一个临时的容器!

举个栗子:

let Parent = document.querySelector("#parent")
for(let i = 0;i<3;i++){
    let Son = document.creatElemet('div')
    Son.innerText = 'Son:'+i
    parent.append(Son)
}

上面的代码,会执行3个循环,三个循环都会对页面进行插入操作!三次都会重复回流!

当次数过多过频繁时,浏览器吃不消了,就可能出现卡顿!

使用文档碎片

let fragment = document.createDocumentFragment()
for(let i = 0;i<3;i++){
    let Son = document.creatElemet('div')
    Son.innerText = 'Son:'+i
    fragment .append(Son)
}
let Parent = document.querySelector("#parent")
Parent.append(fragment )

如此,则仅触发一次回流!

  • 隐藏再显示

如果对某个节点内部需要有很大删改。可以先让节点 display:none 。使

对节点操作完后,再显示回去!

  • 克隆代替

使用 cloneNode 深克隆节点 , 这样子如果此节点操作过久,此期间也不会影响页面!

待完成后代替原节点!

  1. 批量处理样式

对样式频繁的修改,也可能导致多次重复回流!比如:

听说旧一些的浏览器可能会

let node = documet.querySelector("#node")
node.style.width = '100px'
node.style.height= '200px'
node.style.padding= '10px'

因此,应该尽可能的,一次性处理完

  1. 使用 class

通过 className , classList , setAttribute 等API 修改节点的 Class !一次性修改多个样式!

  1. 使用 cssText

cssText 。cssText 可以以字符串方式读取和设置样式。因此一次性直接修改多个样式!

  1. 缓存需要计算计数的数据
let node = documet.querySelector("#node")
for(let i = 0;i<3;i++){
    console.log(node.offsetWidth)
}

上面的代码,模拟了我们需要多个地方使用到 node 的宽度的情况。但每次访问其实都会重新计算 node 的宽度。

因此可以把它缓存起来!

let width= documet.querySelector("#node").offsetWidth
for(let i = 0;i<3;i++){
    console.log(width)
}

2.3 内存优化

内存比较任意出现的问题就是内存泄漏。

以下这些情况,就可能导致内存泄漏,不利于 GC (garbage collection)

或者还保持着无用的数据。占用空间

  • 添加全局变量

变量添加到全局,当变量不使用时,是无法被回收的

  • 变量一直保存对 DOM 的引用

当我们操作DOM 时,会把DOM保存到变量。但操作完,就可以把变量设置为 Null 了。特别是该变量不会及时被回收时。还可能的情况就是,DOM 从页面删除了,但变量还引用着

  • 谨慎使用闭包

闭包的作用之一就是保存变量不被回收。大量使用闭包,可能导致大量的内存无法被及时回收

  • 使用 weakMap 和 weakSet

如果把对象放入 map 或 set 中,很容易忘记去及时的删掉。那么对象就一直被引用则,无法回收

2.4 事件响应优化

  • 使用防抖和节流

  • 使用 requestAnimationFrame

  • 事件代理

  • 及时移除不需要的事件监听

2.5 长任务和复杂计算

  • requestIdleCallback

在浏览器空闲时执行

  • Web worker

开启另一个线程处理复杂任务

3. SEO 优化

3.1 合理的文档描述

  • Title 文本标题,或网站名称
  • Keywords 关键词
  • description 描述
  • robots 设置可以被爬虫读取的文件
  <title>摆烂</title>
  <meta name="keywords" content="摆烂,XXX" />
  <meta name="description" content="一个摆烂的网站" />
  <meta name="robots" content="index" />

3.2 避免使用 iframe

Iframe 的内容不被搜索引擎考虑

3.3 使用语义化标签

语义化标签可以使文档的结构更清晰

3.4 文案处理

使用更可能搜索到的文本和图片,增加被搜索到的概率

3.5 及时的,经常的更新内容

内容的更新也是SEO 参考的一部分

3.6 还有就是,非前端的任务了

比如网站的合理定位,用户多,访问多 ,SEO 也就好了