防抖(debounce)
防抖的原理是:当事件触发时,所触发的回调函数在 n 秒后执行,在这 n 秒内如果事件再次触发,则以新的事件的时间为准,n 秒后再执行新的回调函数。
简单版
保证基本功能
javascript
function debounce(func, wait) {
// 利用闭包储存 setTimeout
var timeout;
return function () {
// 解决 this 在回调函数中指向 window 的问题
var context = this;
// 透传参数
var args = arguments;
// 每次被调用时先清空 setTimeout
clearTimeout(timeout);
// 重新计时
timeout = setTimeout(function () {
func.apply(context, args);
}, wait);
};
}
function debounce(func, wait) {
// 利用闭包储存 setTimeout
var timeout;
return function () {
// 解决 this 在回调函数中指向 window 的问题
var context = this;
// 透传参数
var args = arguments;
// 每次被调用时先清空 setTimeout
clearTimeout(timeout);
// 重新计时
timeout = setTimeout(function () {
func.apply(context, args);
}, wait);
};
}
复杂版
在基本功能基础上增加了立刻执行、取消防抖功能。
立刻执行:当事件触发时,会先执行一次,之后会正常进行防抖。
取消防抖:在防抖计时期间,执行取消会立即执行最后一次回调函数。
javascript
function debounce(func, wait, immediate) {
// 利用闭包储存 setTimeout
var timeout;
var debounced = function () {
// 解决 this 在回调函数中指向 window 的问题
var context = this;
// 透传参数
var args = arguments;
if (immediate && !timeout) {
// 如果有设置立即执行并且是第一次执行,则立即执行
func.apply(context, args);
// 设置 timeout 表示第一次已执行
timeout = setTimeout(function () {
func.apply(context, args);
}, wait);
} else {
// 没有设置立即执行,或者设置立即执行但执行第二次及以后,进行正常防抖
clearTimeout(timeout);
timeout = setTimeout(function () {
func.apply(context, args);
}, wait);
}
};
// 取消防抖功能
debounced.cancel = function () {
// 清空 timeout
clearTimeout(timeout);
timeout = null;
// 立即执行
func.apply(context, args);
};
return debounced;
}
function debounce(func, wait, immediate) {
// 利用闭包储存 setTimeout
var timeout;
var debounced = function () {
// 解决 this 在回调函数中指向 window 的问题
var context = this;
// 透传参数
var args = arguments;
if (immediate && !timeout) {
// 如果有设置立即执行并且是第一次执行,则立即执行
func.apply(context, args);
// 设置 timeout 表示第一次已执行
timeout = setTimeout(function () {
func.apply(context, args);
}, wait);
} else {
// 没有设置立即执行,或者设置立即执行但执行第二次及以后,进行正常防抖
clearTimeout(timeout);
timeout = setTimeout(function () {
func.apply(context, args);
}, wait);
}
};
// 取消防抖功能
debounced.cancel = function () {
// 清空 timeout
clearTimeout(timeout);
timeout = null;
// 立即执行
func.apply(context, args);
};
return debounced;
}
动画版
做动画时也经常用到这种方式,防止在一帧时间中(大概 16ms)多次执行回调函数。
javascript
function debounce(func) {
let sign;
return () => {
cancelAnimationFrame(sign);
sign = requestAnimationFrame(func);
};
}
function debounce(func) {
let sign;
return () => {
cancelAnimationFrame(sign);
sign = requestAnimationFrame(func);
};
}
节流(throttle)
节流的原理是:如果一个事件持续触发,每隔一段时间,只执行一次事件。
简单版
保证基本功能
javascript
function throttle(func, wait) {
// 利用闭包存储 previous 记录上一次的时间戳
var previous = 0;
return function () {
// 取到当前时间戳,+ 可以直接转成毫秒
var now = +new Date();
// 解决 this 在回调函数中指向 window 的问题
var context = this;
// 透传参数
var args = arguments;
if (now - previous > wait) {
// 如果现在的时间戳减去之前执行的时间戳大于所要求的等待时间
// 执行回调函数
func.apply(context, args);
// 记录此次执行的时间戳
previous = now;
}
};
}
function throttle(func, wait) {
// 利用闭包存储 previous 记录上一次的时间戳
var previous = 0;
return function () {
// 取到当前时间戳,+ 可以直接转成毫秒
var now = +new Date();
// 解决 this 在回调函数中指向 window 的问题
var context = this;
// 透传参数
var args = arguments;
if (now - previous > wait) {
// 如果现在的时间戳减去之前执行的时间戳大于所要求的等待时间
// 执行回调函数
func.apply(context, args);
// 记录此次执行的时间戳
previous = now;
}
};
}
复杂版
复杂版可以控制是否立即执行,当事件触发停止后,规定时间后还会触发最后一次
javascript
function throttle(fn, delay, immediate) {
// 利用闭包储存 setTimeout
var timer = null;
// 利用闭包存储第一次时间戳
var previous = +new Date();
return function () {
clearTimeout(timer);
var context = this;
var now = +new Date();
var args = arguments;
if (immediate) {
// 如果有设置立即执行
// 只执行第一次
immediate = false;
fn.apply(context, args);
} else {
// 没有设置立即执行,或者设置立即执行但执行第二次及以后,进行正常节流
if (now - previous >= delay) {
fn.apply(context, args);
previous = now;
} else {
// 当触发事件没有达到所预期时间,setTimeout 设置一次回调,以保证脱离事件后会执行最后一次回调
timer = setTimeout(function () {
fn.apply(context, args);
}, delay);
}
}
};
}
function throttle(fn, delay, immediate) {
// 利用闭包储存 setTimeout
var timer = null;
// 利用闭包存储第一次时间戳
var previous = +new Date();
return function () {
clearTimeout(timer);
var context = this;
var now = +new Date();
var args = arguments;
if (immediate) {
// 如果有设置立即执行
// 只执行第一次
immediate = false;
fn.apply(context, args);
} else {
// 没有设置立即执行,或者设置立即执行但执行第二次及以后,进行正常节流
if (now - previous >= delay) {
fn.apply(context, args);
previous = now;
} else {
// 当触发事件没有达到所预期时间,setTimeout 设置一次回调,以保证脱离事件后会执行最后一次回调
timer = setTimeout(function () {
fn.apply(context, args);
}, delay);
}
}
};
}