本文详细讲解如何从零实现 Underscore 库中的防抖(debounce)函数。通过处理 resize、scroll 等高频事件,解决回调执行频繁导致的性能卡顿。文章逐步演示了如何处理 this 指向、event 对象传递、返回值返回,以及立即执行(immediate)和取消(cancel)功能的完整实现过程。
JavaScript 专题系列首篇,深入解析防抖机制,并引导读者从零开始构建一个符合 Underscore 规范的 debounce 函数。
前言
在前端开发场景中,常需处理高频触发的事件,例如:
- 窗口 resize 与 scroll 事件
- 鼠标 mousedown 与 mousemove 事件
- 键盘 keyup 与 keydown 事件 ……
以下示例代码用于演示事件触发的高频特性:
创建一个 index.html 文件:
<!DOCTYPE html>
<html lang="zh-cmn-Hans">
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="IE=edge, chrome=1">
<title>debounce</title>
<style>
#container{
width: 100%; height: 200px; line-height: 200px; text-align: center; color: #fff; background-color: #444; font-size: 30px;
}
</style>
</head>
<body>
<div id="container"></div>
<script src="debounce.js"></script>
</body>
</html>debounce.js 文件的具体代码如下:
var count = 1;
var container = document.getElementById('container');
function getUserAction() {
container.innerHTML = count++;
};
container.onmousemove = getUserAction;观察运行效果:

debounce
鼠标从左侧滑至右侧,触发 getUserAction 函数高达 165 次!
鉴于此示例极为简单,浏览器能够轻松应对。然而,若涉及复杂的回调函数或 AJAX 请求呢?假设在 1 秒内触发了 60 次,每个回调函数必须在 1000 / 60 = 16.67ms 内完成执行,否则便会出现卡顿现象。
针对此问题,通常采用两种解决方案:
- debounce 防抖
- throttle 节流
防抖
本文重点阐述防抖功能的实现细节。
防抖的核心逻辑为:无论事件触发频率多高,均确保在事件触发 n 秒后执行。若在 n 秒内再次触发,则重置计时器,以最新触发时间为准重新计算 n 秒后执行。简言之,必须等待事件停止触发 n 秒后,才执行回调,策略十分严格。
第一版
基于上述描述,编写第一版代码:
// 第一版
function debounce(func, wait) {
var timeout;
return function () {
clearTimeout(timeout)
timeout = setTimeout(func, wait);
}
}若使用初始示例场景:
container.onmousemove = debounce(getUserAction, 1000);此时无论鼠标如何移动,只要停止操作 1000ms 内无新触发,事件即被执行。
执行次数从 165 次骤降至 1 次!
效果显著,继续完善功能。
this
若在 getUserAction 函数中执行 console.log(this),在未使用 debounce 函数时,this 的值为:
<div id="container"></div>然而,若使用我们实现的 debounce 函数,this 将错误指向 Window 对象!
因此,需修正 this 的指向。
修改代码如下:
// 第二版
function debounce(func, wait) {
var timeout;
return function () {
var context = this;
clearTimeout(timeout)
timeout = setTimeout(function(){
func.apply(context)
}, wait);
}
}此时 this 指向已修正。接着探讨下一个问题:
event 对象
JavaScript 事件处理函数会接收 event 对象,修改 getUserAction 函数如下:
function getUserAction(e) {
console.log(e);
container.innerHTML = count++;
};若不使用 debounce 函数,此处将打印 MouseEvent 对象,如图所示:

MouseEvent
但在我们实现的 debounce 函数中,却仅打印 undefined!
故再次修改代码:
// 第三版
function debounce(func, wait) {
var timeout;
return function () {
var context = this;
var args = arguments;
clearTimeout(timeout)
timeout = setTimeout(function(){
func.apply(context, args)
}, wait);
}
}返回值
需注意,getUserAction 函数可能包含返回值,因此需返回函数的执行结果。
// 第四版
function debounce(func, wait) {
var timeout, result;
return function () {
var context = this;
var args = arguments;
clearTimeout(timeout)
timeout = setTimeout(function(){
result = func.apply(context, args)
}, wait);
return result;
}
}至此,修复了三个关键问题:
- this 指向
- event 对象
- 返回值
立刻执行
当前代码已较完善,但为满足更高需求,思考新场景。
该需求描述为:
不希望强制等待事件停止触发后才执行,而是希望立即执行函数,随后等待 n 秒后,方可重新触发执行。
此需求合理,故添加 immediate 参数以判断是否立即执行。
// 第五版
function debounce(func, wait, immediate) {
var timeout, result;
return function () {
var context = this;
var args = arguments;
if (timeout) clearTimeout(timeout);
if (immediate) {
// 如果已经执行过,不再执行
var callNow = !timeout;
timeout = setTimeout(function(){
timeout = null;
}, wait)
if (callNow) result = func.apply(context, args)
}
else {
timeout = setTimeout(function(){
result = func.apply(context, args)
}, wait);
}
return result;
}
}取消
最后考虑一个小需求:希望能取消 debounce 函数。例如,若 debounce 间隔为 10 秒且 immediate 为 true,则需等待 10 秒后方可重新触发。现希望提供一个按钮,点击后取消防抖,以便再次触发时可立即执行,是否令人欣喜?
为满足此需求,编写最后一版代码:
// 第六版
function debounce(func, wait, immediate) {
var timeout, result;
var debounced = function () {
var context = this;
var args = arguments;
if (timeout) clearTimeout(timeout);
if (immediate) {
// 如果已经执行过,不再执行
var callNow = !timeout;
timeout = setTimeout(function(){
timeout = null;
}, wait)
if (callNow) result = func.apply(context, args)
}
else {
timeout = setTimeout(function(){
result = func.apply(context, args)
}, wait);
}
return result;
};
debounced.cancel = function() {
clearTimeout(timeout);
timeout = null;
};
return debounced;
}演示效果如下:

debounce-cancel
至此,已完整实现 Underscore 中的 debounce 函数,恭喜成功!
专题系列
JavaScript 专题系列:https://www.jb51.net/list/list_3_1.htm
JavaScript 专题系列计划撰写二十余篇,主要研究日常开发中功能点的实现,如防抖、节流、去重、类型判断、拷贝、最值、扁平、柯里、递归、乱序、排序等,特点是研(chao)究(xi) Underscore 和 jQuery 的实现方式。
以上为 JavaScript 专题之 Underscore 防抖实例学习的详细内容,更多关于 JavaScript Underscore 防抖的资料请关注本站其它相关文章!