在开发中会经常遇到一些需要频繁触发的事件的场景。
- 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 秒执行一次

参考:
