在开发中会经常遇到一些需要频繁触发的事件的场景。
- window 的 resize、scroll
- mousedown、mousemove
- keyup、keydown
……
demo
// HTML 代码
<body>
<div id="container">
</div>
</body>
// CSS 代码
<style>
#container {
width: 100%;
height: 200px;
line-height: 200px;
text-align: center;
color: #fff;
background-color: #444;
font-size: 30px;
}
</style>
// javascript 代码
<script>
let count = 1;
let container = document.getElementById("container");
function getUserAction() {
container.innerHTML = count++;
}
container.onmousemove = getUserAction;
</script>
当鼠标滑过时会频繁地触发事件,这个 demo 比较简单,所以浏览器可以完全反应过来,但是如果遇到比较复杂回调函数呢?如 ajax 请求。这样的话就未必能应付过来。
所以需要限制回调函数的触发频率。防抖就是其中一种方法。
防抖的原理:尽管触发事件,但是一定在事件触发 n 秒后才执行回调函数,如果一个事件触发的 n 秒内又触发了这个事件,那么又以这个新的事件的时间为准,n 秒后才执行,总之,就是在触发事件的 n 秒内不再触发事件。
第一版
// 第一版
function debounce(func, wait) {
let timeout;
return function () {
clearTimeout(timeout);
timeout = setTimeout(func, wait);
};
}
以开头 demo 为例
container.onmousemove = debounce(getUserAction, 1000);
不管触发多少次都会在下一次触发时取消上一次的 timeout。
this 和 event
解决 this 和 event。
// 第二版
function debounce(func, wait) {
let timeout;
return function () {
const context = this, args = arguments;
clearTimeout(timeout);
timeout = setTimeout(function () {
func.apply(context, args);
}, wait);
};
}
立即执行
新需求:不希望等到事件停止触发后才执行,希望能立即执行函数,然后等到停止触发 n 秒后,才可以重新触发执行。
// 第三版
function debounce(func, wait, immediate) {
let timeout;
return function () {
const context = this, args = arguments;
if (timeout) clearTimeout(timeout);
if (immediate) {
// 如果已经执行过,不再执行
let callNow = !timeout;
timeout = setTimeout(function () {
timeout = null;
}, wait);
if (callNow) func.apply(context, args);
} else {
timeout = setTimeout(function () {
func.apply(context, args);
}, wait);
}
};
}
返回值
执行的函数可能会有返回值,所以需要返回函数的执行结果,但是当 immediate 为 false 时,因为使用了 setTimeout,所以返回值是 undefined,所以 immediate 为 true 时,才有返回值。
// 第四版
function debounce(func, wait, immediate) {
let timeout, result;
return function () {
const context = this, args = arguments;
if (timeout) clearTimeout(timeout);
if (immediate) {
// 如果已经执行过,不再执行
let callNow = !timeout;
timeout = setTimeout(function () {
timeout = null;
}, wait);
if (callNow) result = func.apply(context, args);
} else {
timeout = setTimeout(function () {
func.apply(context, args);
}, wait);
}
return result;
};
}
cancel
新需求:希望能取消 debounce 函数,比如 debounce 的时间间隔是 10 秒钟,immediate 为 true 时,这样只能再等上 10 秒才能重新触发执行,现在需要一个功能当触发这个功能后,取消防抖,这样再去触发,就可以立即执行。
// 第五版
function debounce(func, wait, immediate) {
let timeout, result;
const debounced = function () {
const context = this, args = arguments;
if (timeout) clearTimeout(timeout);
if (immediate) {
// 如果已经执行过,不再执行
let callNow = !timeout;
timeout = setTimeout(function () {
timeout = null;
}, wait);
if (callNow) result = func.apply(context, args);
} else {
timeout = setTimeout(function () {
func.apply(context, args);
}, wait);
}
return result;
};
debounced.cancel = function () {
clearTimeout(timeout);
timeout = null;
};
return debounced;
}
以 demo 为例
let count = 1;
const container = document.getElementById("container");
function getUserAction() {
container.innerHTML = count++;
}
const setUserAction = debounce(getUserAction, 10000, true);
container.onmousemove = setUserAction;
document.getElementById("btn").addEventListener("click", function () {
setUserAction.cancel();
});
下面是每隔 10 秒执行一次
参考: