这是一个综合性问题,并没有标准的答案,只能尽量全面的总结
性能优化原则
- 所使用内存,缓存或其他办法
- 减少CPU计算量,减少网络加载耗时
- 适用于所有编程的性能优化,空间换时间
从何入手
让加载更快
-
减少资源体积,压缩代码
以webpack为例,当我们把webpack的mode设置为production
模式时,打包出来的代码就是经过压缩的,体积也会减少很多
-
减少访问次数,合并代码,SSR服务端渲染,缓存
还是以webpack为例,我们写代码的时候,可能会有很多JS,但是经过打包后,js文件被整合到一个里边,大大减少了请求次数;当然雪碧图,也是减少访问次数的一种方式。
image.png
缓存
也可以减少访问次数,比如本来要请求10次,但是其中4个可以从缓存获取,那就请求6次就可以了,当然,前提是要命中缓存。
image.png
当我们用webpack打包时,输出文件名配置了
contenthash
,这个时候会根据文件内容生成hash值,拼接到文件名里。之所以这样配置,就是为了缓存:
- 静态资源加hash后缀,根据文件内容计算hash
- 文件内容不变,则hash不变,hash不变则url不变
- url和文件不变,则会自动触发http缓存机制,返回304
- 使用更快的网络 CDN(全称是Content Delivery Network,即内容分发bai网络)
SSR服务端渲染
:将网页和数据一起加载一起渲染
非SSR前后端分离
先加载网页,网页的ajax再去请求数据,服务器返回数据后,再渲染数据
简单的说,就是把原服务器上的数据,赋值到其他服务器上,用户访问时,哪那台服务器离用户近,就访问哪台服务器上的数据,工作中我们经常把静态文件通过CDN引入,比如js,css,图片等
JSP,ASP,PHP
都是SSR,现在的vue react SSR
让渲染更快
- css放在head里边,JS放在body最下边
- 尽早开始执行JS,用DOMContentLoaded触发
- 懒加载 (图片懒加载,上滑加载更多)
懒加载原理
:页面有很多img图片,正常情况下会发起很多次请求,耗费性能。这个时候,可以给img的src设置同一个预览图片,然后再给img设置一个自定义属性,比如叫data-realsrc
,把真正要显示的图片地址赋值给这个属性,然后监听img元素出现在视口的时候,就把data-realsrc
的图片链接赋值给src属性。就实现了懒加载 - 对DOM查询进行缓存(DOM操作是比较耗费性能的)
- 频繁的DOM操作,合并到一起插入DOM结构
- 节流(throttle),防抖(debounce)
防抖(debounce)
概念:不管触发频率多大,都在停止触发后的给定时间触发(涉及到定时器的使用
)
场景:监听一个输入框,输入内容变化后,触发keyup
事件,如果输入速度很快,会频繁触发keyup
事件。这不是我们想要的。
应用:我们的目标是,最后一次输入后500毫秒内,没有再次输入,也就是没有再次触发keyup
事件,才去进行一些操作,比如发请求,具体实现如下:
<input type="text" id="input1">
<script>
const input1 = document.getElementById('input1')
let timer = null;
input1.addEventListener('keyup', function () {
if (timer) {
clearTimeout(timer)
}
timer = setTimeout(() => {
console.log(input1.value)
// 如果过了500毫秒,没有再触发keyup,则计时器内的内容会被执行,执行完之后,计时器就可以取消了
timer = null
},500)
})
</script>
// 当然,这只是一个input,如果有很多input,每个都像上边那样写很多代码,是很浪费的,我们可以封装一下debounce
// 防抖封装
function debounce(fn, delay = 500) {
// timer 是在闭包中的,也是在debounce作用域中
// timer 是不对外暴露的,不能被别人修改
let timer = null
return function () {
if (timer) {
clearTimeout(timer)
}
timer = setTimeout(function () {
fn.apply(this, arguments)
timer = null
},delay)
}
}
// debounce的使用
input1.addEventListener('keyup', debounce(function () {
console.log(input1.value)
},600))
节流(throttle)
概念:不管触发频率多大,都以恒定频率触发(涉及到定时器的使用
)
场景:当拖拽一个元素时,要随时拿到该元素的位置。直接用drag
事件,则会频繁触发,很容易导致卡顿。
应用:使用节流,无论拖拽速度多块,都每隔100ms触发一次
const div1 = document.getElementById('div1')
let timer = null
div1.addEventListener('drag', function (e) {
if (timer) {
return
}
timer = setTimeout(function () {
console.log(e.offsetX, e.offsetY)
timer = null
},100)
})
// 节流函数工具化
function throttle(fn, delay = 100) {
let timer = null
return function () {
if (timer) {
return
}
timer = setTimeout(() => {
fn.apply(this, arguments)
timer = null
},delay)
}
}
div1.addEventListener('drag', throttle(function (e) {
console.log(e.offsetX, e.offsetY)
},200))
无论是防抖还是节流,我们封装的时候,都用了fn.apply(this,arguments),原因如下
那节流为例,当我们调用throttle工具函数的时候,我们给throttle函数传入了一个函数作为第一个参数。而这个函数里还有一个e作为参数,这个e是event事件对象。我们通过事件对象去获取offsetX和offsetY。
image.png
注意正常情况下,当我们触发了事件,然后事件对象会被作为参数传给事件处理程序,这个时候的function里的e是直接被传进去的。因为可以直接用e
image.png
而当我们使用throttle的时候,我们给throttle传了一个function(e){xxx}函数作为fn,这个函数里是接收不到e对象的,因为e这个时候会传给throttle的返回函数。实际情况也是这样,我们打印arguments里可以看到 dragEvent
image.png
然后,我们只有通过fn.apply把reutrn 函数的argument换给fn,才可以在fn里获取到e对象。
网友评论