前言
防抖和节流是闭包的经典使用场景。要理解防抖和节流,必须先理解闭包。
防抖和节流一般在 resize 或 scroll 时使用,避免处理事件无限调用,影响用户体验。至于使用防抖还是节流根据业务需要。
防抖-debounce
事件触发后,只在最后执行一次。
function debounce(fn, wait) {
let timer = null
return function(...args) {
// 使用了外部函数的变量timer,这是一个不会被销毁的闭包。
// 事件触发过程中,该函数会不停的执行,一直给timer赋值,再clearTimeout
// 直到最后一次触发,会有一个定时任务放入事件队列执行。
if(timer) clearTimeout(timer)
timer = setTimeout(() => {
fn.apply(this, args)
}, wait)
}
}
// 处理函数
function handle() { console.log(Math.random())}
// 滚动事件
window.addEventListener('scroll', debounce(handle, 1000));
节流-throttle
事件触发后,每隔一段时间,触发一次。
时间戳:
var throttle = function(func, delay) {
var prev = Date.now();
return function() {
var context = this;
var args = arguments;
var now = Date.now();
if (now - prev >= delay) {
// 1. 这里使用了外部函数变量prev,是一个不会被销毁的闭包
// 2. 事件执行过程中,prev的值不变,但now的值每次执行都会重新赋值,是会变的
// 3. 当大于delay时,会执行一次func,再给prev重新赋值。
// 4. prev重新赋值后,重复2、3过程,达到每隔一段时间执行一次。
func.apply(context, args);
prev = Date.now();
}
}
}
// 处理函数
function handle() { console.log(Math.random())}
// 滚动事件
window.addEventListener('scroll', throttle(handle, 1000));
定时器实现:
var throttle = function(func, delay) {
let timer = null
return function() {
var context = this;
var args = arguments;
if(!timer) {
// 1. 使用了外部函数变量timer,是一个不会被销毁的闭包
// 2. timer为空时,赋值一个定时器
// 3. 等到定时器执行回调,func执行一次,timer赋值为空
// 4. 下次事件触发,再给timer赋值一个定时器,达到每隔一段时间执行一次
timer = setTimeout(function() {
func.apply(context, args)
timer = null
}, delay)
}
}
}
时间戳 + 定时器:
大家自行理解哈,“时间戳 + 定时器” 的方式保证了事件最后一次触发后,会再执行一个定时器。
var throttle = function(func, delay) {
let timer = null
let startTime = Date.now()
return function() {
var context = this;
var args = arguments;
let curTime = Date.now()
let remaining = delay - (curTime - startTime)
clearTimeout(timer)
if (remaining <= 0) {
func.apply(context, args);
startTime = Date.now();
} else {
timer = setTimeout(function() {
func.apply(context, args)
timer = null
}, delay)
}
}
}
clearTimeout 都是在回调还没执行的时候将定时器清除的。
总结:
防抖和节流是个老生常谈的问题了,难点是要理解闭包的原理,这个就简单了。