提问:什么是防抖(debounce)?
回答:触发事件后在 n 秒内函数只能执行一次,如果在 n 秒内又触发了事件,则会重新计算函数执行时间。即短时间内大量触发同一事件,只会执行一次函数。
提问:什么是节流(throttle)?
回答:连续高频触发事件但是在 n 秒中只执行一次函数。防抖是延迟执行, 而节流是间隔执行,函数节流即每隔一段时间就执行一次。节流会稀释函数的执行频率。
一、防抖
普通防抖函数(非立即执行)
特点:每次执行过程中不执行,等到执行后过一段时间再执行。
原理:设置一个定时器,约定在一段特定时期之后再触发事件处理,每次触发事件都会重新设置计时器,直到一段时间内无第二次操作为止。
function debounce(fn, delay) {
let timeout = null
return function () {
let context = this//保存this指向
let args = arguments//拿到event对象
if (timeout) clearTimeout(timeout) // 清除延时器
timeout = setTimeout(() => {
fn().apply(context, args)
}, delay)
}
}
立即执行防抖函数:
特点:刚开始时立即执行一次,之后只会在过了指定时间以后才能再次执行。
原理:设置一个定时器,约定在一段特定时期之后再触发事件处理,每次触发事件都会重新设置计时器,直到一段时间内无第二次操作为止。
function debounce(fn, delay,immediate) {
let timeout = null
return function () {
let context = this//保存this指向
let args = arguments//拿到event对象
if (timeout) clearTimeout(timeout) //清除延时器
if (immediate) {
let rightNow = !timeout
//第一次会立即执行,以后只有事件执行后才会再次触发
timeout = setTimeout(function(){
timeout = null
},delay)
if (rightNow) {
fn().apply(context, args)
}
}
else{
timeout = setTimeout(function(){
fn().apply(context,args)
},delay)
}
}
}
二、节流
方法一:时间戳写法
特点:事件会立即执行,但停止触发后将不再被执行。
原理:使用两个时间戳,startTime表示函数刚执行时的旧时间戳,endTime表示函数执行完后的新时间戳,每次触发事件判断二者的时间差,如果能满足约束条件就执行函数并重置旧时间戳。
function throttle(fn, delay = 1000) {
let startTime = 0
return function () {
let endTime = Date.now()
let context = this
let args = arguments
if (endTime - startTime >= delay) {
fn().apply(context, args)
startTime = Date.now()
}
}
}
方法二:定时器写法
特点:delay毫秒后第一次执行,第二次事件停止触发后依然会再一次执行。
原理:设置一个定时器,约定一段时间后执行函数,约定时间到了之后执行函数并重置定时器。
function throttle(fn, delay = 1000) {
let timeout = null
return function () {
let context = this
let args = arguments
if (!timeout) {
timeout = setTimeout(()=>{
fn().apply(context, args)
timer = null
}, delay)
}
}
}
方法三:时间戳与定时器相结合
特点:将两种写法的特性相结合,实现一个更为精确的节流。
function throttle(fn, delay = 1000) {
let timeout = null
let startTime = 0
return function () {
let endTime = Date.now() //获取当前时间
let remains = delay - (endTime - startTime)
let context = this
let args = arguments
clearTimeout(timer)
if (remains <= 0) {
fn().apply(context, args)
startTime = Date.now()
} else {
timeout = setTimeout(fn, remains)
}
}
}
三、防抖与节流的区别
共同点:
1、都可以通过使用延时计时器 setTimeout 来实现。
2、其目的都是为了降低回调执行频率,以此节省计算时所占用的内存资源。
不同点:
1、防抖函数在一段连续操作结束后,处理回调,利用clearTimeout和 setTimeout实现。函数节流,在一段连续操作中,每一段时间只执行一次,会在频率较高的事件中使用来提高性能。
2、防抖函数关注一定时间连续触发的事件,只在最后执行一次,而函数节流一段时间内只执行一次。
四、应用场景
防抖:在连续的事件中只需触发一次回调的场景
1、搜索框搜索输入,只需用户最后一次输入完再发送请求。
2、用户名,手机号,邮箱输入验证时的输入框搜索自动补全事件,搜索框搜索输入。只需用户最后一次输入完,再发送请求。
3、频繁操作点赞和取消点赞。
4、浏览器窗口大小改变后只需窗口调整完成后再执行resize事件中的代码,防止重复渲染。
5、高频触发的事件监听回调:比如onscroll, onresize, oninput, touchmove等;
节流:在间隔一段时间执行一次回调的场景
1、滚动加载,加载更多或滚到底部监听,轮播图等。
2、搜索框在输入内容时每多输入几个字符都会给出不同的相关搜索提示。