防抖(debounce):每次触发定时器后,取消上一个定时器,然后重新触发定时器。防抖一般用于用户未知行为的优化,比如搜索框输入弹窗提示,因为用户接下来要输入的内容都是未知的,所以每次用户输入就弹窗是没有意义的,需要等到用户输入完毕后再进行弹窗提示。
节流(throttle):每次触发定时器后,直到这个定时器结束之前无法再次触发。一般用于可预知的用户行为的优化,比如为scroll事件的回调函数添加定时器。
防抖在连续的事件,只需触发一次回调的场景有:
1.搜索框搜索输入。只需用户最后一次输入完,再发送请求
2.手机号、邮箱验证输入检测
3.窗口大小resize。只需窗口调整完成后,计算窗口大小。防止重复渲染。
节流在间隔一段时间执行一次回调的场景有:
1.滚动加载,加载更多或滚到底部监听
2.搜索框,搜索联想功能
1.接收什么参数?
参数1: 回调函数
参数2: 延迟时间
function mydebounce(fn, delay){
}
2.返回什么
最终返回结果是要绑定对应的 监听事件,所以一定是个新函数
function mydebounce(fun, delay) {
const _debounce = () => {
}
return _debounce
}
3.内部怎么实现
可以在 _debounce 函数中开启一个定时器,定时器的延迟时间就是 参数delay
每次开启定时器时,都需要将上一次的定时器取消掉
function mydebounce(fn, delay){
// 1. 创建一个变量,记录上一次定时器
let timer = null
// 2. 触发事件时执行函数
const _debounce = () => {
// 2.1 取消上一次的定时器,(第一次执行时,timer应该为null)
if(timer) clearTimeout(timer)
// 2.2 延迟执行传入fn的回调
timer = setTimeout(() => {
fn()
// 2.3 函数执行完成后,需要将timer重置
timer = null
},delay)
}
return _debounce
}
以上,一个基本的防抖函数就实现了
对其进行测试
Document
在上面的测试中,当我们持续在input框中输入数据时,控制台只会每隔500ms进行输出
虽然上面实现了基本的防抖函数,但是我们会发现代码中this的指向是window,而window没有value值,所以无法知道事件的调用者
function mydebounce(fn, delay){
let timer = null
// 1. 箭头函数不绑定this,修改为普通函数
const _debounce = function(){
if(timer) clearTimeout(timer)
timer = setTimeout(() => {
// 2. 使用显示绑定apply方法
fn.apply(this)
timer = null
},delay)
}
return _debounce
}
在事件监听的函数,是有可能传入一些参数的,例如 event等
function mydebounce(fn, delay){
let timer = null
// 1. 接收可能传入的参数
const _debounce = function(...args){
if(timer) clearTimeout(timer)
timer = setTimeout(() => {
// 2. 将参数传给fn
fn.apply(this,args)
timer = null
},delay)
}
return _debounce
}
测试:
为什么要增加取消操作呢?
例如,用户在表单输入的过程中,返回了上一层页面或关闭了页面,就意味着这次延迟的网络请求没必要继续发生了,所以可以增加一个可取消发送请求的功能
function mydebounce(fn, delay){
let timer = null
const _debounce = function(...args){
if(timer) clearTimeout(timer)
timer = setTimeout(() => {
fn.apply(this,args)
timer = null
},delay)
}
// 给_debounce添加一个取消函数
_debounce.cancel = function(){
if(timer) clearTimeout(timer)
}
return _debounce
}
测试:
有些场景需要第一次输入时,立即执行,后续的输入再使用防抖
// 1.设置第三个参数,控制是否需要首次立即执行
function mydebounce(fn, delay, immediate = false){
let timer = null
// 2. 定义变量,用于记录状态
let isInvoke = false
const _debounce = function(...args){
if(timer) clearTimeout(timer)
// 3.第一次执行不需要延迟
if(!isInvoke && immediate){
fn.apply(this,args)
isInvoke = true
return
}
timer = setTimeout(() => {
fn.apply(this,args)
timer = null
// 4.重置isInvoke
isInvoke = false
},delay)
}
_debounce.cancel = function(){
if(timer) clearTimeout(timer)
// 取消也需要重置
timer = null
isInvoke = false
}
return _debounce
}
测试:
function mydebounce(fn, delay){
let timer = null
const _debounce = function(...args){
if(timer) clearTimeout(timer)
timer = setTimeout(() => {
fn.apply(this,args)
timer = null
},delay)
}
return _debounce
}
function mydebounce(fn, delay, immediate = false){
let timer = null
let isInvoke = false
const _debounce = function(...args){
if(timer) clearTimeout(timer)
if(!isInvoke && immediate){
fn.apply(this,args)
isInvoke = true
return
}
timer = setTimeout(() => {
fn.apply(this,args)
timer = null
isInvoke = false
},delay)
}
_debounce.cancel = function(){
if(timer) clearTimeout(timer)
timer = null
isInvoke = false
}
return _debounce
}
需要接受参数
参数1:要执行的回调函数
参数2:要执行的间隔时间
function myThrottle(fn, interval){
}
返回值
返回值为一个新的函数
function myThrottle(fn, interval){
const _throttle = function(){
}
return _throttle
}
内部实现
如果要实现节流函数,利用定时器不太方便管理,可以用时间戳获取当前时间nowTime
参数 : 开始时间 StartTime 和 等待时间waitTime,间隔时间 interval
waitTIme = interval - (nowTime - startTime)
得到等待时间waitTime,对其进行判断,如果小于等于0,则可以执行回调函数fn
开始时间可以初始化为0,第一次执行时,waitTime一定是负值(因为nowTime很大),所以第一次执行节流函数,一定会立即执行
function myThrottle(fn, interval){
// 1. 定义变量,保存开始时间
let startTime = 0
const _throttle = function(){
// 2. 获取当前时间
const nowTime = new Date().getTime()
// 3. 计算需要等待的时间
const waitTime = interval - (nowTime - startTime)
// 4.当等待时间小于等于0,执行回调
if(waitTime <= 0){
fn()
// 并让开始时间 重新赋值为 当前时间
startTime = nowTime
}
}
return _throttle
}
测试:
同样的,首先是对this指向和参数的优化
function myThrottle(fn, interval){
let startTime = 0
const _throttle = function(...args){
const nowTime = new Date().getTime()
const waitTime = interval - (nowTime - startTime)
if(waitTime <= 0){
fn.apply(this,args)
startTime = nowTime
}
}
return _throttle
}
上面的代码中,第一次执行 肯定是 立即执行的,因为waitTime第一次必为负数,但有时需要控制 第一次不要立即执行
// 1. 设置第三个参数控制是否立即执行
function myThrottle(fn, interval, immediate = true){
let startTime = 0
const _throttle = function(...args){
const nowTime = new Date().getTime()
// 2. 控制是否立即执行
if(!immediate && startTime === 0){
startTime = nowTime
}
const waitTime = interval - (nowTime - startTime)
if(waitTime <= 0){
fn.apply(this,args)
startTime = nowTime
}
}
return _throttle
}
测试:
function myThrottle(fn, interval){
let startTime = 0
const _throttle = function(...args){
const nowTime = new Date().getTime()
const waitTime = interval - (nowTime - startTime)
if(waitTime <= 0){
fn.apply(this,args)
startTime = nowTime
}
}
return _throttle
}
function myThrottle(fn, interval, immediate = true){
let startTime = 0
const _throttle = function(...args){
const nowTime = new Date().getTime()
if(!immediate && startTime === 0){
startTime = nowTime
}
const waitTime = interval - (nowTime - startTime)
if(waitTime <= 0){
fn.apply(this,args)
startTime = nowTime
}
}
return _throttle
}
参考:https://blog.csdn.net/m0_71485750/article/details/125581466