JS进阶10 -- 防抖和节流
在 JS进阶9 --JS设计模式中我们学习了JS中的常用设计模式,并对其应用场景以及在开发和主流框架中的实际应用进行了深入剖析。本文将继续跟大家介绍JS中的第10个重点内容 – 防抖和节流。废话不多说,一起来看看吧!
JS进阶10 --防抖和节流
防抖
防抖的适用场景
常见的前端性能优化方案 , 它可以防止JS高频渲染页面时出现的视觉抖动(卡顿):比如
- 示例1:页面改变尺寸时,同步调整图表的大小
- 示例2:输入内容时,结合ajax进行搜索并渲染结果
如果内容的渲染速度过快,都可能会造成抖动效果,并且连带会浪费性能。
- 频繁执行逻辑代码,耗费浏览器性能
- 频繁发送请求去服务器,耗费服务器性能
适用场景: - 在触发频率高的事件中
- 频率高的事件: resize、input 、scroll 、keyup….
- 执行耗费性能操作
- 耗费性能的操作: 操纵页面、网络请求….
- 需要实现的效果: 连续操作之后只有最后一次生效
这个时候就可以适用防抖来进行优化
手写防抖
需求
防抖优化之后的效果可以通过一些具体的网站来进行确认,比如12306,他就是通过防抖进行的优化:
- 在输入内容的时候没有发送请求
- 输入完毕之后,稍等一会才发送请求去服务器
这个就是防抖的效果: 连续事件停止触发后,一段时间内没有再次触发,就执行业务代码。
核心实现步骤
- 开启定时器,保存定时器id
- 清除已开启的定时器
那个输入框+搜索的例子优化之后代码如下:
let timeId
document.querySelector('.search-city').addEventListener('input', function () {
// 2. 清除已开启的定时器
clearTimeout(timeId)
// 1. 开启定时器,保存定时器id
timeId = setTimeout(() => {
renderCity(this.value)
}, 500)
})
lodash的debounce方法
实际开发中一般不需要手写防抖,因为已经有库里面提供了对应的方法,可以直接调用,也可以自己手写实现debounce
,一会咱们就从这两个方面进行讲解:
lodash
工具库中的debounce
方法(常用)debounce
的实现原理(高频面试题)
传送门
_.debounce(func, [wait=0], [options=])
参数
func
(Function): 要防抖动的函数。[wait=0]
(number): 需要延迟的毫秒数。[options=]
(Object): 选项对象。[options.leading=false]
(boolean): 指定在延迟开始前调用。[options.maxWait]
(number): 设置func
允许被延迟的最大值。[options.trailing=true]
(boolean): 指定在延迟结束后调用。
返回
(Function): 返回新的 debounced(防抖动)函数。
注意:
- 实际开发时一般给前2个参数即可,然后使用返回的函数替换原函数即可。
- 项目中如果有
lodash
那么直接使用它提供的debounce
即可,不仅可以实现防抖,原函数中的this
和参数
均可以正常使用。
手写debounce函数
手写实现debounce
函数,实现lodash
中debounce
方法的核心功能。
需求
- 参数:
func
(Function): 要防抖动的函数。[wait=0]
(number): 需要延迟的毫秒数。
- 返回值:
- (Function): 返回新的 debounced(防抖动)函数。
核心步骤
- 返回防抖动的新函数
- 原函数中的
this
可以正常使用 - 原函数中的参数可以正常使用
function debounce(func, wait = 0) {
let timeId
// 防抖动的新函数
return function (...args) {
let _this = this
clearTimeout(timeId)
timeId = setTimeout(function () {
// 通过apply调用原函数,并指定this和参数
func.apply(_this, args)
}, wait)
}
}
节流
节流的适用场景
常见的前端性能优化方案 , 它可以防止高频触发事件造成的性能浪费:比如
- 播放视频时同步缓存播放时间
- 如果要多设备同步,还需要通过
ajax
提交到服务器
高频触发耗费性能的操作,会造成性能浪费
适用场景: 在触发频率高的事件中, 执行耗费性能操作 , 连续触发 , 单位时间内只有一次生效。
优化之前: 每当触发事件就会执行业务逻辑。
优化之后: 触发事件之后延迟执行逻辑,在逻辑执行完毕之前无法再次触发。
手写节流
使用节流将播放器记录时间的例子优化:
核心步骤:
1.开启定时器,并保存 id
2.判断是否已开启定时器,若已开启,则直接返回
3.定时器执行时 , id设置为空
// 播放器案例优化之后代码
let timeId
video.addEventListener('timeupdate', function () {
if (timeId !== undefined) {
return
}
timeId = setTimeout(() => {
console.log('timeupdate触发')
localStorage.setItem('currentTime', this.currentTime)
timeId = undefined
}, 3000)
})
lodash的throttle方法
实际开发中一般不需要手写节流,因为已经有库里面提供了对应的方法,可以直接调用,也可以自己手写实现throttle
,一会咱们就从这两个方面进行讲解:
lodash
工具库中的throttle
方法(常用)throttle
的实现原理(高频面试题)
传送门
_.throttle(func, [wait=0], [options=])
参数
func
(Function): 要节流的函数。[wait=0]
(number): 需要节流的毫秒。[options=]
(Object): 选项对象。[options.leading=true]
(boolean): 指定在节流开始前调用。[options.trailing=true]
(boolean): 指定在节流结束后调用。
返回
(Function): 返回节流的函数。
注意:
- 实际开发时一般会给3个参数即可,然后使用返回的函数替换原函数即可。
- 参数3:
options.leading=true
默认为true
,开始时触发节流函数,一般设置为false
。
- 参数3:
- 项目中如果有
lodash
那么直接使用它提供的throttle
即可,不仅可以实现节流,原函数中的this
和参数
均可以正常使用。
// 播放器案例使用`lodash` 优化之后的结果如下
const func = function (e) {
console.log('timeupdate触发')
console.log('e:', e)
localStorage.setItem('currentTime', this.currentTime)
}
const throttleFn = _.throttle(func, 1000, { leading: false })
video.addEventListener('timeupdate', throttleFn)
手写throttle方法
手写实现throttle
函数,实现lodash
中throttle
方法的核心功能。
需求
- 参数:
func
(Function): 要节流的函数。[wait=0]
(number): 需要节流的毫秒。
- 返回值:
- (Function): 返回节流的函数。
核心步骤
- 返回节流的新函数
- 原函数中的
this
可以正常使用 - 原函数中的参数可以正常使用
// 节流工具函数
function throttle(func, wait = 0) {
let timeId
return function (...args) {
if (timeId !== undefined) {
return
}
const _this = this
timeId = setTimeout(() => {
func.apply(_this, args)
timeId = undefined
}, wait)
}
}
总结
防抖和节流的区别
- 函数防抖:每次事件触发都会对应一个定时器,只不过如果是高频次触发,只执行最后一个;
- 函数节流:无论触发多少次事件,在指定的等待时间内,只会生成一个定时器