JavaScript防抖函数实现原理与详细步骤解析

作者:青柠

时间:2026-10-25

浏览:386

本文通过实例详解JavaScript防抖函数的实现原理。从基础定时器方案入手,逐步解决事件对象传递、arguments参数处理及this指向问题。深入解析延迟防抖与前缘防抖的区别,提供完整代码实现与关键步骤说明,帮助开发者掌握高性能事件处理技巧。

文章目录

本文将通过一个具体案例,循序渐进地讲解防抖的实现过程。在理解实现方式的同时,还将深入探讨 this、apply 和 arguments 等 JavaScript 核心概念在实际开发中的具体应用。

为什么需要防抖?

在用户交互中,频繁触发某些事件往往没有实际意义。这不仅会浪费系统资源,影响页面性能,还可能导致界面卡顿,影响用户体验。

为了解决上述问题,我们需要引入防抖机制来优化事件处理逻辑。

什么是防抖?

防抖debounce:

简单来说,防抖的核心效果是:在指定的时间间隔内,无论事件被触发多少次,最终只执行一次回调函数。

如何应用防抖?

首先,我们通过一个具体的实例来引入话题。

假设我们要设置一个按钮,用于切换上方文本的颜色,HTML结构如下:

为什么需要防抖?

    <h2 id="demo">hello</h2>
    <button id="btn">点我</button>
 
    <script type="text/javascript">
        var btn = document.getElementById('btn');
 
        btn.addEventListener('click', changeColor, false);
        
        var flag = true
        function changeColor() {
            if (flag) {
                document.getElementById('demo').style.color = 'pink'
            } else {
                document.getElementById('demo').style.color = 'blue'
            }
            flag = !flag
        }

如果我们希望限制用户在一秒内只能切换一次颜色,即在一秒内多次点击只生效一次。

此时可以利用定时器来实现,但直接编写一个简单的定时器是不够的,因为它只能实现多次触发的延时执行,而无法真正限制触发的频率。

我们可以引入一个变量来标记是否已经触发过定时器。如果定时器已存在且未结束,则清除该定时器并重新创建一个新的一秒定时器;如果定时器不存在,说明一秒钟内没有操作,直接创建一个新的定时器即可。

按照上述逻辑,即可实现基础的防抖效果,具体代码实现如下:

    <h2 id="demo">hello</h2>
    <button id="btn">点我</button>
 
    <script type="text/javascript">
        var btn = document.getElementById('btn');
 
        btn.addEventListener('click', debounce(changeColor), false);
 
        var flag = true
        function changeColor() {
            if (flag) {
                document.getElementById('demo').style.color = 'pink'
            } else {
                document.getElementById('demo').style.color = 'blue'
            }
            flag = !flag
        }
 
        function debounce(fn) {
            let t = null
            return function () {
                //如果定时器存在就清除掉
                if (t) {
                    clearTimeout(t)
                }
                //不然就创建新的定时器
                t = setTimeout(function() {
                    fn()
                }, 1000)
            }
        }
</script>

阅读上述代码后,读者可能会产生以下疑问:

1. 为什么 debounce 函数内部不直接执行逻辑,而是要返回一个函数?

解答:因为我们将 changeColor 函数作为参数传递给了 debounce 函数(参考后续代码变化)。当在按钮上绑定 debounce 事件时,必须在 debounce 内部将改造后的 changeColor 逻辑封装为回调函数返回。

// btn.addEventListener('click', changeColor, false);
btn.addEventListener('click', debounce(changeColor), false);

2. let t = null; 每次执行都会将 t 重置为 null,这如何实现防抖逻辑?

解答:你可能认为按钮绑定的是 debounce,因此每次触发都会执行 t = null。

实际上,按钮绑定的是 debounce(changeColor)!注意这里的括号至关重要。每次触发 click 事件时,执行的是返回的回调函数,而 let t = null 仅在初始化 debounce 时执行一次。

虽然上述代码实现了防抖效果,但你会发现 changeColor 函数无法获取事件对象,即它拿不到本该属于它的 event。

为什么需要防抖?

为了让 changeColor 能够获取到事件对象,我们可以进行如下修改:

1. 既然 event 对象已经存在于 debounce 的回调中,可以将 e 作为参数传递给 debounce 返回的回调函数。

2. 再将 e 传递给回调函数中定时器内的 fn(),即调用 fn(e)。

function debounce(fn) {
            let t = null
            //往这里传e
            return function (e) {
                if (t) {
                    clearTimeout(t)
                }
 
                t = setTimeout(function() {
                    //再带给fn
                    fn(e)
                }, 1000)
            }
        }

经过上述修改,changeColor 函数成功获取到了事件对象。

为什么需要防抖?

有人可能会问,获取这个 e 对象有什么用?

获取 e 对象后,你可以对其执行操作(例如通过 e.target 修改属性)。如果不使用 e,不传递它也可以,但这样写能使防抖函数更加完善。

然而,我们不能保证 changeColor 只有一个参数。如果只传递 e,无法实现多参数的传递。为了进一步完善,我们需要继续修改代码。

提到多参数传递,自然会想到实参列表 arguments。没错,就是它!

如果不了解 arguments,建议查阅相关文档深入学习。

为什么需要防抖?

arguments对象是所有(非箭头)函数中都可用的局部变量。你可以使用arguments对象在函数中引用函数的参数。此对象包含传递给函数的每个参数,第一个参数在索引0处。

首先,我们尝试用 arguments[0] 替换之前传递的 e,结果发现:

为什么需要防抖?

return 语句中的 arguments[0] 无法传递到定时器内部,更不用说传递到 changeColor 中了。

这是因为每个函数内部的 arguments 都是独立的,定时器内的函数没有自己的 arguments 对象,因此值为 undefined。

要解决此问题,可以通过赋值方式保存 arguments,或者使用箭头函数。

接下来,我们采用箭头函数的方式来解决这个问题:

由于箭头函数内部没有自己的 arguments 对象,它会向外层作用域查找,从而能够获取 return 语句中的 arguments。

function debounce(fn) {
            let t = null
            return function () {
                console.log('我是回调的arguments',arguments[0]);
                if (t) {
                    clearTimeout(t)
                }
                
                //这里用箭头函数
                t = setTimeout(() => {
                    fn(arguments[0])
                    console.log('我是定时器里的arguments', arguments[0])
                    // console.log(this)
                }, 1000)
            }
        }

为什么需要防抖?

这样便解决了 arguments[0] 的传递问题。如果是多个参数,可以使用 apply 方法将整个 arguments 作为参数传递过去,代码如下:

function debounce(fn) {
            let t = null
            return function () {
                if (t) {
                    clearTimeout(t)
                }
 
                t = setTimeout(() => {
                    fn.apply(this, arguments)                    
                }, 1000)
            }
        }

其中,apply 方法的第一个参数还可以将 changeColor 的 this 从 Window 转为 btn,这是一举两得的优化。由于箭头函数会继承外层的 this,因此可以将 return 中的 this 传递给 changeColor。

如果不了解 apply,建议查阅相关文档深入学习。

至此,一个延迟执行的 debounce 函数便诞生了!

什么是延迟防抖?

顾名思义,延迟防抖是指在延迟时间结束的那一刻才触发回调函数。

如果你觉得每次点击后都要等待才能改变颜色太麻烦,甚至可能没等到颜色变化就关闭了网页。

前缘防抖可以解决这个问题!(即在定时器开始的那一刻就立即触发事件)

对代码进行进一步修改,即可得到前缘防抖版本:

function debounce(fn) {
            let t = null
            return function () {
                // 用firstClick来记录每一次定时器开始的第一次按下的动作
                var firstClick = !t
 
                if(t) {
                    clearTimeout(t)
                }
 
                if(firstClick) {
                    fn.apply(this, arguments)
                }
 
                //等待1秒后将 t 置为 null,在这1秒内如果再点击事件就会去到if(t)的执行
                t = setTimeout(() => {
                    t = null
                }, 1000)
            }
        }

前缘防抖的效果是:每次连续点击后,先立即响应一次事件,然后再开始等待一秒。

总结

本文详细介绍了 JavaScript 防抖的具体实现及详细原理步骤。如需了解更多相关实现内容,请搜索本站历史文章或继续浏览后续相关文章。希望大家多多支持本站!

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章