防抖、节流
开发中经常会有一些持续触发的事件,比如scroll、resize、mousemove、input等。频繁的执行回调,不仅对性能有很大影响,甚至会有相应跟不上,造成页面卡死等现象。
针对这种问题有两种解决方案,防抖和节流。
防抖
事件触发后的time时间内只执行一次。原理是维护一个延时器,规定在time时间后执行函数,如果在time时间内再次触发,则取消之前的延时器重新设置。所以回调只在最后执行一次。
- 方式一:
function debounce(func, time = 0) {
if (typeof func !== 'function') {
throw new TypeError('Expect a function')
}
let timer
return function () {
if (timer) {
clearTimeout(timer)
}
timer = setTimeout(() => {
func()
}, time)
}
}
- 方式二:
const debounce = (func, wait = 0) => {
let timeout = null
let args
function debounced(...arg) {
args = arg
if(timeout) {
clearTimeout(timeout)
timeout = null
}
// 以Promise的形式返回函数执行结果
return new Promise((res, rej) => {
timeout = setTimeout(async () => {
try {
const result = await func.apply(this, args)
res(result)
} catch(e) {
rej(e)
}
}, wait)
})
}
// 允许取消
function cancel() {
clearTimeout(timeout)
timeout = null
}
// 允许立即执行
function flush() {
cancel()
return func.apply(this, args)
}
debounced.cancel = cancel
debounced.flush = flush
return debounced
}
节流
在事件触发过程中,每隔wait执行一次回调。
可选参数三 trailing
,事件第一次触发是否执行一次回调。默认true,即第一次触发先执行一次。
- 方式一:
function throttle(func, wait = 0, trailing = true) {
if (typeof func !== 'function') {
throw new TypeError('Expected a function')
}
let timer
let start = +new Date
return function (...rest) {
let now = +new Date
if (timer) {
clearTimeout(timer)
}
if (now - start >= wait || trailing) { // 每个wait时间执行一次或第一次触发就执行一次
func.apply(this, rest)
start = now
trailing = false
} else {
// 事件结束wait时间后再执行一次
timer = setTimeout(() => {
func(...rest)
}, wait)
}
}
}
- 方式二:
const throttle = (func, wait = 0, execFirstCall) => {
let timeout = null
let args
let firstCallTimestamp
function throttled(...arg) {
if (!firstCallTimestamp) firstCallTimestamp = new Date().getTime()
if (!execFirstCall || !args) {
// console.log('set args:', arg)
args = arg
}
if (timeout) {
clearTimeout(timeout)
timeout = null
}
// 以Promise的形式返回函数执行结果
return new Promise(async (res, rej) => {
if (new Date().getTime() - firstCallTimestamp >= wait) {
try {
const result = await func.apply(this, args)
res(result)
} catch (e) {
rej(e)
} finally {
cancel()
}
} else {
timeout = setTimeout(async () => {
try {
const result = await func.apply(this, args)
res(result)
} catch (e) {
rej(e)
} finally {
cancel()
}
}, firstCallTimestamp + wait - new Date().getTime())
}
})
}
// 允许取消
function cancel() {
clearTimeout(timeout)
args = null
timeout = null
firstCallTimestamp = null // 这里很关键。每次执行完成后都要初始化
}
// 允许立即执行
function flush() {
cancel()
return func.apply(this, args)
}
throttled.cancel = cancel
throttled.flush = flush
return throttled
}
区别: 不管事件触发有多频繁,节流都会保证在规定时间内一定会执行一次真正的事件处理函数,而防抖只是在最后一次事件触发后才执行一次函数。
场景:
防抖:比如监听页面滚动,滚动结束并且到达一定距离时显示返回顶部按钮,适合使用防抖。
节流:比如在页面的无限加载场景下,需要用户在滚动页面时过程中,每隔一段时间发一次 Ajax 请求,而不是在用户停下滚动页面操作时才去请求数据。此场景适合用节流来实现。
网友评论