性能调优
文章目录
原始链接
建议去印象笔记中查看:https://app.yinxiang.com/fx/858b24e1-21bf-4493-8e82-51d4ae76e9e0
从输入URL到页面解析经历的过程
- DNS解析
- TCP连接
- HTTP请求发出
- 服务器解析并返回内容
- 浏览器解析服务器内容,并渲染网页
DNS解析优化
DNS 的预解析或者 TCP 协议的负载均衡
TCP连接优化
HTTP请求优化
减少请求次数
减少请求体积
webpack优化
- 让loader做更少的事情
module: {
rules: [
{
test: /\.js$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
}
]
}
- 开启loader缓存
loader: 'babel-loader?cacheDirectory=true'
- 使用dllPlugin进行优化
作用是把依赖的包进行一次打包,后续webpack只需要打包自己业务代码即可。 - Happypack将loader进行多线程操作
- 配置按需加载
- 开启Gzip压缩
1.webpack和服务器都开启Gzip压缩
2.如果服务器找不到Gzip开启服务器动态压缩
图片优化
jpeg
优点
- 有损压缩,在压缩图片的同时质量损失最小。
缺点
- 对小图,细节图处理不够,一压缩图片细节就不行了
- 无法设置透明
使用场景
1.大图
2.banner图
png
优点
- 无损压缩,保证图片质量,png-8和png-24两种,对图片处理比较细节
- 可以设置透明色
缺点 - 图片过大
使用场景
1.小图标
2.logo - 细节处理较强的图片
svg
优点
- 放大多少倍都不会失真
缺点
- 不好处理大图
使用场景
1.icon
雪碧图
base64
优点
- 减少图片请求
缺点
- 增大图片体积
- 使用base64后图片会增大4/3
使用场景
1.小图,
webP
1.兼容性不高
浏览器缓存机制介绍与缓存策略剖析
chrome介绍
通过网络获取内容既速度缓慢又开销巨大。较大的响应需要在客户端与服务器之间进行多次往返通信,这会延迟浏览器获得和处理内容的时间,还会增加访问者的流量费用。因此,缓存并重复利用之前获取的资源的能力成为性能优化的一个关键方面。
浏览器缓存请求优先级顺序
- Memory Cache
- Service Worker Cache
- HTTP Cache
- Push Cache
缓存机制
强缓存
强缓存是利用 http 头中的 Expires 和 Cache-Control 两个字段来控制的。强缓存中,当请求再次发出时,浏览器会根据其中的 expires 和 cache-control 判断目标资源是否“命中”强缓存,若命中则直接从缓存中获取资源,不会再与服务端发生通信。
expires
设置过期时间,由服务端返回一个具体时间,如下格式:
expires: Wed, 11 Sep 2019 16:12:18 GMT
当客户端在次发起请求的时候将判断客户端时间与该时间,如果小于该时间则不再请求服务器,直接从浏览器中拿去。
弊端为如果客户端时间与服务器时间一致性较差将导致该功能不太准确
Cache-Control
设置过期倒计时秒数,由服务端返回为具体秒数,如下格式:
cache-control: max-age=31536000
当客户端发起请求后服务端返回cache-control字段,通过max-age记录时间,客户端再起请求后会拿当前时间和浏览器记录的上次请求时间+max-age作比较,如果小于该时间则不再请求服务器。
public 与 private
public 与 private 是针对资源是否能够被代理服务缓存而存在的一组对立概念。
如果我们为资源设置了 public,那么它既可以被浏览器缓存,也可以被代理服务器缓存;如果我们设置了 private,则该资源只能被浏览器缓存。private 为默认值
noCache
no-cache 绕开了浏览器:我们为资源设置了 no-cache 后,每一次发起请求都不会再去询问浏览器的缓存情况,而是直接向服务端去确认该资源是否过期(即走我们下文即将讲解的协商缓存的路线)。
noStore
no-store 比较绝情,顾名思义就是不使用任何缓存策略。在 no-cache 的基础上,它连服务端的缓存确认也绕开了,只允许你直接向服务端发送请求、并下载完整的响应。
协商缓存
协商缓存依赖于服务端与浏览器之间的通信。
协商缓存机制下,浏览器需要向服务器去询问缓存的相关信息,进而判断是重新发起请求、下载完整的响应,还是从本地获取缓存的资源。
Last-Modified
Last-Modified是一个时间戳,如果我们开启协商缓存,那在我们第一次请求资源的时候respHeader会返回该字段,当我们下次在请求该资源的时候,会在请求头加上
If-Modified-Since: Fri, 27 Oct 2017 06:35:57 GMT
值为上一次返回的Last-Modified,服务端会判断该时间戳与资源最后一次修改时间是否一致,如果一致,返回304,不一致返回新的内容,并且重新返回Last-Modified
弊端
1.如果我们编辑了文件,但是实际没有改动,依然后导致重新请求,不走缓存
2.如果我们编辑文件过快,If-Modified-Since是以秒为单位的,所以可能会存在问题
Etag
Etag 是由服务器为每个资源生成的唯一的标识字符串,这个标识字符串是基于文件内容编码的,只要文件内容不同,它们对应的 Etag 就是不同的,反之亦然。因此 Etag 能够精准地感知文件的变化。
Etag 的生成过程需要服务器额外付出开销,会影响服务端的性能,这是它的弊端。因此启用 Etag 需要我们审时度势。正如我们刚刚所提到的——Etag 并不能替代 Last-Modified,它只能作为 Last-Modified 的补充和强化存在。 Etag 在感知文件变化上比 Last-Modified 更加准确,优先级也更高。当 Etag 和 Last-Modified 同时存在时,以 Etag 为准。
HTTP 缓存决策指南
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RbBEf5Aw-1646194856300)(en-resource://database/519:1)]
本地存储
- cookie
- localStorage
- SessionStorage
- indexedDB
CDN
概念
简单理解就是我把资源copy放到各个地方的服务器,当你发出请求的时候,会找到离你最近的服务器来进行请求,这样就可以提升访问的速度。
核心特点
缓存
把资源copy到cdn服务器上的操作
回源
当检查到资源过期后转头像根服务器要资源的过程
cdn使用场景
我们通常把静态资源放到cdn服务器上,例如JS,CSS,图片等内容,不需要后端进行运算的资源,
cdn优化中的小细节
当我们在访问淘宝的时候会发现他的资源是从多个不同的域名请求过来的
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0gb3FlN7-1646194856302)(en-resource://database/520:1)]
这么做带来了几个好处:
- 如果是在一个域名下的话,由于cookie的同源,会导致请求图片等不需要权限校验的资源会携带cookie增加了不必要的消耗
- 不同的域名会打破浏览器请求数量限制,同一个域名同时最多可以处理8(不同浏览器不一样)个请求,当域名不同是 可以解决掉这个问题(http1.1有这个问题,到http2就没了)
服务端渲染(SSR)
什么是客户端渲染
服务端渲染与之相对的就是客户端渲染,客户端渲染可以理解为用户请求html后把里面的js跑一遍,根据结果渲染页面,请求的html里面基本上是没有dom结果的,渲染的内容根据js的结果不同而不同。
什么是服务端渲染
服务端会把页面内容进行组装和拼接,用户拿到的html基本上就可以直接在浏览器上渲染的内容。
为什么用服务端渲染
- 增强seo
- 性能优化,减少首屏加载时间
弊端
对服务器压力较大,如果软件使用量大,会导致服务器压力增大
浏览器运行机制
浏览器内核由 渲染引擎和 JS 引擎组成
常见的浏览器内核
- Trident(IE)
- Gecko(火狐)
- Blink(Chrome、Opera)
- Webkit(Safari)
浏览器内核的组成
- HTML 解释器:将 HTML 文档经过词法分析输出 DOM 树。
- CSS 解释器:解析 CSS 文档, 生成样式规则。
- 图层布局计算模块:布局计算每个对象的精确位置和大小。
- 视图绘制模块:进行具体节点的图像绘制,将像素渲染到屏幕上。
- JavaScript 引擎:编译执行 Javascript 代码。
浏览器渲染过程解析
- 解析HTML 中的标签使其变成dom树。
- 解析css创建cssdom树.(与1同时进行)
- 12 的结果进行合并生成渲染树
- 从根节点递归调用,计算每一个元素的大小、位置等,给每个节点所应该出现在屏幕上的精确坐标,我们便得到了基于渲染树的布局渲染树
- 遍历渲染树,每个节点将使用 UI 后端层来绘制。整个过程叫做绘制渲染树
tips
- css选择器是从右往左的匹配的
- 避免使用通配符,只对需要用到的元素进行选择。
- 关注可以通过继承实现的属性,避免重复匹配重复定义。
- 少用标签选择器。如果可以,用类选择器替代
css与js阻塞优化
css阻塞
css是会阻塞dom树的渲染的,所以哪怕我们dom树渲染完成,css树没有渲染完成的话 页面上也是不会出现内容的。
这个时候我们就想办法提前加载css和 尽快加载css于是启用了两种手法。
- 将css提前放到header中
- 使用CDN加载css,尽可能缩短css加载时间
js阻塞
js同样会阻塞css和dom树的渲染,因为浏览器无法预料到js执行完后 会有什么改变,所以可以理解为遇到script标签后,js引擎会抢占渲染引擎,优先执行js,所以我们通常的做法是,在最后加载script除非这个script标签不得不在中间出现
js的多种加载方式
正常模式
<script src="index.js"></script>
这种情况下 JS 会阻塞浏览器,浏览器必须等待 index.js 加载和执行完毕才能去做其它事情。
async 模式
<script async src="index.js"></script>
async 模式下,JS 不会阻塞浏览器做任何其它的事情。它的加载是异步的,当它加载结束,JS 脚本会立即执行。
defer 模式
<script defer src="index.js"></script>
defer 模式下,JS 的加载是异步的,执行是被推迟的。等整个文档解析完成、DOMContentLoaded 事件即将被触发时,被标记了 defer 的 JS 文件才会开始依次执行
从应用的角度来说,一般当我们的脚本与 DOM 元素和其它脚本之间的依赖关系不强时,我们会选用 async;当脚本依赖于 DOM 元素和其它脚本的执行结果时,我们会选用 defer。
DOM优化
DOM优化思路
减少dom的操作
- dom操作会导致回流和重绘
- dom操作的时候JS引擎与渲染引擎通信需要成本
异步更新策略(Event Loop)
执行顺序
宏任务(macroTask)=>微任务(microTask)=>更新UI=>执行webWorker
回流与重绘
回流
当我们对DOM属性的几何尺寸(宽高,位置等信息)进行修改可能会引起回流。浏览器从新计算几何属性然后重新绘制。
重绘
当我们对DOM的非几何属性(颜色,背景色等)进行操作的时候会触发重绘。
重绘不一定导致回流,回流一定会导致重绘
如何避免回流
- 将计算结果缓存起来,最好一次在操作DOM
- 避免逐条改变样式,使用class来进行操作
- DOM 离线化,就是将DOM先用display操作,然后再显示,虽然也会增加回流但是同2类似,可以减少整体的回流次数
会导致回流的操作
- 修改几何尺寸
- 增减节点
- offsetTop、offsetLeft、 offsetWidth、offsetHeight、scrollTop、scrollLeft、scrollWidth、scrollHeight、clientTop、clientLeft、clientWidth、clientHeight使用这些属性的时候(不得已的操作,因为需要获取的是实时结果)
tips
- chrome 会有一个flush队列来控制我们做回流操作,也就是说不是每一次改变几何尺寸都会触发回流,一般会在任务多起来,或者一定时间内进行回流flush队列。当然也有不得已的情况,即为上述3的操作。
首屏优化(lazy Load)
参考:https://juejin.cn/post/6844903455048335368
核心原理:
先用一个div占用空间,判断图片是否滚动到可是区域内,如果滚动到了替换图片资源,并且记录下标减少重复加载的几率,同时使用节流进行相关优化。
throttle(节流)debounce(防抖)
节流含义
当事件第一次触发后,在一定时间内将不会再触发。
防抖含义
当事件触发后再一定时间内如果事件在此触发则更新时间,知道时间结束时间没有被触发才开始执行。
tips
防抖和节流函数核心都是用定时器实现的
throttle节流代码实现
// fn是我们需要包装的事件回调, interval是时间间隔的阈值
function throttle(fn, interval) {
// last为上一次触发回调的时间
let last = 0
// 将throttle处理结果当作函数返回
return function () {
// 保留调用时的this上下文
let context = this
// 保留调用时传入的参数
let args = arguments
// 记录本次触发回调的时间
let now = +new Date()
// 判断上次触发的时间和本次触发的时间差是否小于时间间隔的阈值
if (now - last >= interval) {
// 如果时间间隔大于我们设定的时间间隔阈值,则执行回调
last = now;
fn.apply(context, args);
}
}
}
// 用throttle来包装scroll的回调
const better_scroll = throttle(() => console.log('触发了滚动事件'), 1000)
document.addEventListener('scroll', better_scroll)
debounce节流代码实现
// fn是我们需要包装的事件回调, delay是每次推迟执行的等待时间
function debounce(fn, delay) {
// 定时器
let timer = null
// 将debounce处理结果当作函数返回
return function () {
// 保留调用时的this上下文
let context = this
// 保留调用时传入的参数
let args = arguments
// 每次事件被触发时,都去清除之前的旧定时器
if(timer) {
clearTimeout(timer)
}
// 设立新定时器
timer = setTimeout(function () {
fn.apply(context, args)
}, delay)
}
}
// 用debounce来包装scroll的回调
const better_scroll = debounce(() => console.log('触发了滚动事件'), 1000)
document.addEventListener('scroll', better_scroll)
Performance、LightHouse 与性能 API
- LightHouse是一个性能检测插件