前端性能优化

前言

关于前端性能优化这个问题大家常常会遇到,在项目中往往也是一道坎,特别是面试中也是被常常提及,那我想大家对性能优化可能都不会太陌生吧,但是普遍对它又知之甚少。
性能优化也是对前端人员的技术上的一个提升,是个人技术成长的必由之路,所以构建一个性能优化的体系是很有必要,在平时的阅读过程中看到了许多优秀的文章,整合了各家所长本文由此诞生的,希望对自己对大家的成长能所帮助,特此记录一下这篇学习笔记。

性能指标

如果想要知道一个页面的性能,那首先要知道衡量的指标,页面再渲染的过程中会有哪些指标呢?
image.png
性能优化主要分为

  • 加载时优化:网站加载的速度如 白屏时间、首屏时间
  • 运行时优化:运行时性能是指页面运行时的性能表现

加载时优化

  • 白屏时间:指的是从输入网址, 到页面开始显示内容的时间。
  • 首屏时间:指从输入网址, 到首屏页面内容渲染完毕的时间。

白屏时间计算

将代码脚本放在 前面就能获取白屏时间:

<script>
  new Date().getTime() - performance.timing.navigationStart
  </script>

首屏时间计算

在window.onload事件中执行以下代码,可以获取首屏时间:

new Date().getTime() - performance.timing.navigationStart

网址的输入到页面渲染

这个问题我想不少人在刷面试题的时候都遇到过吧,说一说从输入URL到页面渲染的整个过程…

  • DNS 域名解析
  • 找到对应的 IP 地址
  • 三次握手与服务器建立 TCP 连接
  • 浏览器发送 HTTP 请求
  • 服务器接收请求
  • 服务器处理请求并返回 HTTP 报文
  • 浏览器接收并解析渲染页面

渲染时优化

在整个页面渲染期间可以做到的优化点:

1. DNS 的解析期间优化:

通过 DNS预解析优化、DNS 负载均衡,缩短浏览器访问DNS的时间。
在DNS 解析过程中客户机先去查找本机的/etc/hosts 文件,是否有对应的解析记录,
若本机没有找到则去本地域名服务器,再没有向根服务器发送递归查找,
从 边缘DNS > 顶级DNS > 二级DNS > 三级DNS 依次向上查找image.png
DNS预解析的实现:
用meta信息来告知浏览器, 当前页面要做DNS预解析:

<meta http-equiv="x-dns-prefetch-control" content="on" />

在页面header中使用link标签来强制对DNS预解析:

<link rel="dns-prefetch" href="http://bdimg.share.baidu.com" />

注意:dns-prefetch需慎用,多页面重复DNS预解析会增加重复DNS查询次数。

2. 使用HTTP2

解析速度快 多路复用 首部压缩 服务器推送

3. 减少HTTP请求数量

HTTP的请求建立和释放需要时间。
HTTP请求从建立到关闭一共经过以下步骤:

  1. 客户端连接到Web服务器
  2. 发送HTTP请求
  3. 服务器接受请求并返回HTTP响应
  4. 释放连接TCP链接

这些步骤都是需要花费时间的,在网络情况差的情况下,花费的时间更长。
如果页面的资源非常碎片化,每个HTTP请求只带回来几K甚至不到1K的数据(比如各种小图标)那性能是非常浪费的。

4. 压缩、合并文件,减少http请求大小

  • 压缩文件 -> 减少HTTP请求大小,可以减少请求时间 对html、css、js以及图片资源进行压缩处理
  • 文件合并 -> 减少HTTP请求数量。 提取公共代码
  • 压缩资源
    • js压缩:UglifyPlugin
    • CSS压缩:MiniCssExtractPlugin
    • HTML压缩:HtmlWebpackPlugin
    • 图片压缩:image-webpack-loader

5. 服务器端渲染 SSR

客户端渲染: 获取 HTML 文件,根据需要下载 JavaScript 文件,运行文件,生成 DOM,再渲染。
服务端渲染:服务端返回 HTML 文件,客户端只需解析 HTML
优点:首屏渲染快,SEO 好。
缺点:配置麻烦,增加了服务器的计算压力。

6. 静态资源使用CDN

用户与服务器的物理距离对响应时间也有影响。把内容部署在多个地理位置分散的服务器上能让用户更快地载入页面, CDN就是为了解决这一问题,在多个位置部署服务器,让用户离服务器更近,从而缩短请求时间。

7. 资源缓存

不重复加载相同的资源

图片懒加载:

先不给图片设置路径,当图片出现在浏览器可视区域时才设置真正的图片路径。
实现上就是先将图片路径设置给original-src,当页面不可见时,图片不会加载:

<img original-src="https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/9eb06680a16044feb794f40fc3b1ac3d~tplv-k3u1fbpfcp-watermark.image" />

通过监听页面滚动,等页面可见时设置图片src:

const img = document.querySelector('img')
img.src = img.getAttribute("original-src")

如果想使用懒加载,还可以借助一些已有的工具库,例如 aFarkas/lazysizes、verlok/lazyload、tuupola/lazyload 等。

css中图片懒加载

除了对于 元素的图片进行来加载,在 CSS 中使用的图片一样可以懒加载,最常见的场景就是 background-url。

.login {
    background-url: url(/static/img/login.png);
}

对于上面这个样式规则,如果不应用到具体的元素,浏览器不会去下载该图片。
所以你可以通过切换 className 的方式,放心得进行 CSS 中图片的懒加载。

8. 使用 Defer 异步加载JS

尽量**将 CSS 放在文件头部,JavaScript 文件放在底部,**所有放在 head 标签里的 CSS 和 JS 文件都会堵塞渲染。
如果这些 CSS 和 JS 需要加载和解析很久的话,那么页面就空白了。
所以 JS 文件要放在底部,等 HTML 解析完了再加载 JS 文件,只要给 script 标签加上 defer 属性就可以实现异步下载、延迟执行。

运行时优化

1. 减少重绘与重排

浏览器渲染流程
  1. 解析HTML生成DOM树
  2. 解析CSS生成CSSOM规则树
  3. 将DOM树与CSSOM规则树合并生成Render(渲染)树
  4. 遍历Render(渲染)树开始布局, 计算每一个节点的位置大小信息
  5. 将渲染树每个节点绘制到屏幕上

image.png
重绘

当重新生成渲染树后, 将要将渲染树每个节点绘制到屏幕, 这个过程叫重绘。

重排

当改变DOM元素位置或者大小时, 会导致浏览器重新生成Render树, 这个过程叫重排

重排触发时机
重排发生后的根本原理就是元素的几何属性发生改变, 所以从能够改变几何属性的角度入手:

  • 添加 | 删除可见的DOM元素
  • 元素位置发生改变
  • 元素本身的尺寸发生改变
  • 内容变化
  • 页面渲染器初始化
  • 浏览器窗口大小发生改变

二者关系:重排会导致重绘, 但是重绘不会导致重排

减少重绘重排

  1. 避免 table 布局: 会造成整个 table 重新布局
  2. DOM 的读写操作分离
  3. 集中改变样式
  4. position 为 absolute 或 fixed

2. 避免页面卡顿

浏览器渲染更新页面的标准帧率也为60次/s - -60FPS(frames/pre second), 那么每一帧的预算时间约为16.7ms ≈ 1s/60,浏览器在这个时间内要完成所有的整理工作,如果无法符合此预算, 帧率将下降,内容会在屏幕抖动, 此现象通常称为卡顿
浏览器在刷屏的过程中的工作流程: image.png

  1. 用js做了些逻辑,触发了style的样式变化
  2. style把应用的样式规则计算好之后,把影响到的页面元素进行重新布局,叫做layout
  3. 再把它画到内存的一个画布里面,paint成了像素,
  4. 最后把这个画布刷到屏幕上去,叫做composite,形成一帧。

这几项的任何一项如果执行时间太长了,就会导致渲染这一帧的时间太长,平均帧率就会掉。
当然上面的过程并不一定每一步都会执行,例如:

  • 你的js只是做一些运算,并没有增删DOM或改变CSS,那么后续几步就不会执行
  • style只改了颜色等不需要重新layout的属性就不用执行layout这一步
  • style改了transform属性,在blink和edge浏览器里面不需要layout和paint

3. 长列表优化

实现虚拟列表
虚拟列表是一种用来优化长列表的技术。它可以保证在列表元素不断增加,或者列表元素很多的情况下,依然拥有很好的滚动、浏览性能。
它的核心思想在于:只渲染可见区域附近的列表元素。 即 滚动加载效果
常用的框架也有不错的开源实现, 如:

  • 基于React的 react-virtualized
  • 基于Vue 的 vue-virtual-scroll-list
  • 基于Angular的 ngx-virtual-scroller

4. 滚动事件性能优化

由于滚动事件发生非常频繁,所以频繁地执行监听回调就容易造成JavaScript执行与页面渲染之间互相阻塞的情况。
对应滚动这个场景,可以采用 防抖 和 节流 来处理,可以参考这篇文章 来手写一次防抖节流吧!

防抖

在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时,连续触发一个函数,只执行最后一次。

当一个事件频繁触发,而我们希望在事件触发结束一段时间后(此段时间内不再有触发)才实际触发响应函数时会使用防抖(debounce)。例如用户一直点击按钮,但你不希望频繁发送请求,你就可以设置当点击后 200ms 内用户不再点击时才发送请求。

节流

当频繁的触发一个事件,每隔一段时间内, 只会执行一次事件。

当一个事件频繁触发,而我们希望间隔一定的时间再触发相应的函数时, 就可以使用节流(throttle)来处理。比如判断页面是否滚动到底部,然后展示相应的内容;就可以使用节流,在滚动时每300ms进行一次计算判断是否滚动到底部的逻辑,而不用无时无刻地计算。

5. 使用 Web Workers

通过计算切片,使用 setTimeout 拆分密集型任务,但是有些计算无法利用此方法拆解,同时还可能产生副作用,这个方法需要视具体场景而动.

利用Web Worker 进行多线程编程.
Web Worker 是一个独立的线程(独立的执行环境),这就意味着它可以完全和 UI 线程(主线程)并行的执行 js 代码,从而不会阻塞 UI,它和主线程是通过 onmessage 和 postMessage 接口进行通信的。
Web Worker 使得网页中进行多线程编程成为可能。当主线程在处理界面事件时,worker 可以在后台运行,帮你处理大量的数据计算,当计算完成,将计算结果返回给主线程,由主线程更新 DOM 元素。

6. 代码上的优化

  1. 使用事件委托
    绑定的事件越多, 浏览器内存占有就越多,从而影响性能,利用事件代理的方式就可节省一些内存。
  2. if-else 对比 switch
    当判定条件越来越多时, 越倾向于使用switch,而不是if-else, switch只要进行一次判断即可
  3. 布局上使用flexbox
    提升性能

参考

  1. 万字长文:分享前端性能优化知识体系
  2. 我的前端性能优化体系
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值