JavaScript防抖与节流:原理、实现及应用场景详解

作者:代码拾荒时间:2026-10-26 00:04:05浏览:477

本文深入解析JavaScript中防抖(debounce)与节流(throttle)的核心原理及实现方式。通过延迟与前缘两种模式的对比,结合代码示例,详细阐述两者在减少服务器请求、提升页面性能方面的具体应用场景,帮助开发者掌握控制函数执行频率的关键技术。

1. 前言

首先举例说明。在百度首页的搜索框中,用户输入时,百度服务器会实时返回联想关键词。每修改一个字,联想词就会更新一次,这是用户可见的速度。实际上,若不加以控制,可能已向服务器发起数十次相同的联想请求,具体频率取决于不同PC的运行速度。正如前述,对同一关键词发起多次请求,用户可能只需一次结果,其余均为无效请求。若成千上万甚至上亿用户同时请求,将给服务器带来巨大负担。

防抖节流解决的问题:

在高频连续触发的事件中,若不加控制地频繁调用事件处理函数,会显著增加浏览器或服务器的负载,进而影响用户体验。常见的连续触发场景包括:

例如浏览器滚动条的滚动事件(scroll)、窗口大小调整事件(resize)、输入框的内容校验以及移动端的触摸移动事件(touchmove)等。

为了解决上述问题,我们通常采用防抖函数(debounce)和节流函数(throttle)来限制事件处理函数的调用频率。

总体而言,防抖和节流都是在时间轴上对函数的执行次数进行控制的手段。

2. 函数防抖(debounce)

延迟防抖

延迟防抖(debounce)的定义是:当事件被触发后,等待n秒再执行回调;如果在这n秒内事件再次被触发,则重新计时。

生活中的例子是电梯:有人进入电梯(触发事件),电梯将在10秒后启动(执行监听器);如果在这10秒内又有人进入(再次触发),电梯将重新等待10秒后启动(重新计时)。

当持续触发事件时,必须保证在一段时间内没有再次触发事件,事件处理函数才会执行一次。

如果设定的时间到来之前,又一次触发了事件,就重新开始延时。

如下图所示,当持续触发click事件时,handle函数并不会立即执行;只有当1000毫秒内没有再次触发click事件时,才会延时执行click事件。

1. 前言

前缘防抖

前缘防抖的策略是:先执行动作,然后设定一个周期;在周期内有事件被触发时,不执行动作,且周期重新设定。

为什么要这样设计呢?

考虑第一种延迟防抖的情况:我们原本希望降低对用户输入关键字发起联想请求的频率。但如果用户在设定的时间内持续输入,用户将一直看不到返回结果。相比之下,第一次输入时立即发起请求并展示结果,后续输入在停止后继续请求,体验会更佳。

1. 前言

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>防抖</title>
</head>
<body>
    <button id="debounce1">点我防抖呐!</button>
<script>
    function handle() {
      console.log("防抖成功!");
    }
    window.onload = function() {
        // 1. 获取按钮,并绑定事件
        var myDebounce = document.getElementById('debounce1');
        myDebounce.addEventListener('click',debounce(handle,1000,true));
    }
    // 防抖函数
    function debounce(fn,wait, immediate ){
        //2. 设置时间戳,使用setTimeout让返回函数延迟执行
        let timer, result;
        return function(...args){
            // 3. timer存在,将定时器中的函数清除
            if(timer) clearTimeout(timer);
            // 4.1 立即执行返回函数
            if(immediate){
                if(!timer){
                    result = fn.apply(this,args);
                }
                timer = setTimeout(() => {
                    timer = null;
                },wait);
            }else{  // 4.2 非立即执行返回函数
                timer = setTimeout(() => {
                    fn.apply(this,args);
                },wait);
            }
        }
        // 5. 立即执行时返回函数的返回值
        return result;
    }
</script>
</body>
</html>

实现效果:

1. 前言

原理解析:

  • 防抖函数的作用是对传入的函数进行延时包装后返回。
  • 当setTimeout在前一次未执行完前,第二次触发将会覆盖掉前面的定时器,从而执行第二次的功能。
  • 由于前一次调用因异步加延时还未执行完,使用clearTimeout清除前面的定时器,以取消上次的fn功能。
  • 为了保持fn内部this的指向,使用apply改变this指向。
  • 注意fn传入的是函数本身,而不是函数的调用。

防抖函数实现总结

function debounce(fn,wait, immediate ){
    //2. 设置时间戳,使用setTimeout让返回函数延迟执行
    let timer, result;
    return function(...args){
        // 3. timer存在,将定时器中的函数清除
        if(timer) clearTimeout(timer);
        // 4.1 立即执行返回函数
        if(immediate){
            if(!timer){
                result = fn.apply(this,args);
            }
            timer = setTimeout(() => {
                timer = null;
            },wait);
        }else{  // 4.2 非立即执行返回函数
            timer = setTimeout(() => {
                fn.apply(this,args);
            },wait);
        }
    }
    // 5. 立即执行时返回函数的返回值
    return result;
}

3. 函数节流(throttling)

throttling(节流)的策略是:在固定周期内,只执行一次动作;若有新事件触发,不执行。周期结束后,若有新事件触发,则开始新的周期。节流策略同样分为前缘和延迟两种模式。

与debounce类似,延迟模式指周期结束后执行动作,前缘模式指执行动作后再开始周期。

  • 节流会稀释函数的执行频率。
  • 在持续触发事件的过程中,函数会立即执行,并且每n秒执行一次。

生活中的例子:目前有一种说法是,当1秒内连续播放24张以上的图片时,在人眼的视觉中就会形成一个连贯的动画。因此,电影播放(以前是,现在不知道)基本以每秒24张的速度播放。如果不采用24张而采用100张,虽然画面更流畅,但会显得非常浪费资源。

延迟节流

1. 前言

前缘节流

1. 前言

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>节流</title>
</head>
<body>
    <button id="debounce1">点我节流呐!</button>
<script>
    function handle() {
      console.log("节流成功!");
    }
    window.onload = function() {
        var myDebounce = document.getElementById('debounce1');
        myDebounce.addEventListener('click',throttling(handle,1000,false));
    }
    // 节流函数
    function throttling(fn,wait,immediate){
        let timer;
        return function(...args) {
            if(!timer){
                if(immediate){
                    fn.apply(this,args);
                }
                timer = setTimeout(() => {
                    if(!immediate) {
                        fn.apply(this,args);
                    }
                    timer = null;
                },wait);
            }
        }
    }
</script>
</body>
</html>

实现效果:

1. 前言

节流函数实现总结

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>节流</title>
</head>
<body>
    <button id="debounce1">点我节流呐!</button>
<script>
    function handle() {
      console.log("节流成功!");
    }
    window.onload = function() {
        var myDebounce = document.getElementById('debounce1');
        myDebounce.addEventListener('click',throttling(handle,1000,false));
    }
    // 节流函数
    function throttling(fn,wait,immediate){
        let timer;
        return function(...args) {
            if(!timer){
                if(immediate){
                    fn.apply(this,args);
                }
                timer = setTimeout(() => {
                    if(!immediate) {
                        fn.apply(this,args);
                    }
                    timer = null;
                },wait);
            }
        }
    }
</script>
</body>
</html>

4. 两者区别

函数节流不管事件触发有多频繁,都会保证在规定时间内一定会执行一次真正的事件处理函数。

函数防抖只是在最后一次事件后才触发一次函数。

例如在页面的无限加载场景下,我们需要用户在滚动页面时,每隔一段时间发一次Ajax请求,而不是在用户停下滚动页面操作时才去请求数据。这样的场景,就适合用节流技术来实现。

5. 应用场景

对于函数防抖,有以下几种应用场景:

  • 给按钮加函数防抖防止表单多次提交。
  • 对于输入框连续输入进行AJAX验证时,用函数防抖能有效减少请求次数。
  • 判断scroll是否滑到底部,滚动事件+函数防抖

总的来说,适合多次事件一次响应的情况

对于函数节流,有如下几个场景:

  • 游戏中的刷新率
  • DOM元素拖拽
  • Canvas画笔功能

总的来说,适合大量事件按时间做平均分配触发。

总结

本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注本站的更多内容!

作者最新文章