JavaScript专题(三)防抖

JavaScript专题(三)防抖在前端开发中会遇到一些频繁的事件触发 比如 resize scroll mousedown mousemove keyup keydown 今天我们在实现属于自己的防抖函数

防抖

目录

一、为什么需要防抖

  • 高频的函数操作可能产生不好的影响
  • 如:resize、scroll、mousedown、mousemove、keyup、keydown……

为此,我们举个示例代码来了解事件如何频繁的触发:

我们写一个 index.html 文件:

<html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document 
     title> <title>debounce 
      title> <style> #wrapper { 
       width: 100%; height: 200px; line-height: 200px; text-align: center; color: #fff; background-color: #444; font-size: 30px; }  
       style>  
        head> <body> <div id="wrapper"> 
         div> <script> var count = 1; var oDiv = document.getElementById("wrapper"); function getUserAction() { 
          oDiv.innerHTML = count++; } oDiv.onmousemove = getUserAction;  
          script>  
           body>  
            html> 

从左边滑到右边就触发了近100次getUserAction 函数!看如下Gif:

在这里插入图片描述

因为这个例子很简单,所以浏览器完全反应的过来,但假设:

  • 它的触发频次极高,1分钟2000次,且涉及到大量的位置计算、DOM 操作等工作,
  • 存在接口请求,单个函数执行时间较长,但每个函数触发的间隔很近。

这种在一瞬间(短时间内)对浏览器或服务器造成了过多压力的交互就需要进行优化了,为了解决这个问题,一般有两种解决方案:

  • debounce 防抖
  • throttle 节流

他们的目的都是:降低一个函数的触发频率,以提高性能或避免资源浪费。

二、防抖的原理

今天重点讲讲防抖的实现。

防抖的原理就是:你尽管触发事件,但是我一定在事件触发n秒无操作后才执行。举个例子:

我们规定3s为防抖的标准,那么:

  1. 第一次要求执行事件 – 此时倒计时3s
  2. 倒计时2s
  3. 倒计时1s
  4. 0.5s时事件再次被触发 – 此时倒计时3s
  5. …3s内无事发生
  6. 执行事件,共用了5.5s

JavaScript专题(三)防抖

三、自己实现一个防抖

3.1 第一版

我们根据上一节提到的核心思想,实现第一版代码:

function debounce(func, wait) { 
    var timer; return function () { 
    clearTimeout(timer) timer = setTimeout(func, wait); } } 

如果我们要使用它,第一节的例子为例:

oDiv.onmousemove = debounce(getUserAction, 2000); 

此时大家可以再次测试一下,事件持续发生时,只有在完全停止2s后,才会触发事件:

写到这里,作为针对部分高频事件的需求来说,已经结束了。我们来看看他的效果:

在这里插入图片描述

3.2 第二版

大家都知道,dom节点在触发事件的时候,this指向它本身,本例中则指向oDiv,但是在本例中:我们看一下

var count = 1; var oDiv = document.getElementById("oDiv"); function getUserAction() { 
    oDiv.innerHTML = count++; console.log('this', this); // 此时输出 Window... } oDiv.onmousemove = debounce(getUserAction, 2000); function debounce(func, wait) { 
    var timer; return function () { 
    clearTimeout(timer) timer = setTimeout(func, wait); } } 

毕竟经过了一层匿名函数的包裹,this已经指向了window,为了减少影响,我们尝试修正它

function debounce(func, wait) { 
    var timer; return function () { 
    var _this = this; // 记录当前this clearTimeout(timer) timer = setTimeout(function(){ 
    func.apply(_this); //将 func的this改为_this }, wait); } } 
第三版

解决的this指向问题,我们的函数仍然不够“完美”,JavaScript中,事件处理函数会提供event对象,我们简称为e。

// 使用了 debouce 函数 function getUserAction(e) { 
    console.log(e); // undefined oDiv.innerHTML = count++; }; 

为了保证它的原汁原味,我们再改第三版:

var count = 1; var oDiv = document.getElementById("oDiv"); function getUserAction(e) { 
    oDiv.innerHTML = count++; console.log('e', e); // MouseEvent } oDiv.onmousemove = debounce(getUserAction, 2000); function debounce(func, wait) { 
    var timer; return function () { 
    var _this = this; // 记录当前this var arg = arguments; // 记录参数 clearTimeout(timer) timer = setTimeout(function () { 
    func.apply(_this, arg); //将 func的this改为_this }, wait); } } 

到此为止,我们在尽可能保留Dom事件原有能力的情况下,给函数加上了防抖效果,它可以解决大部分我们日常开发的防抖问题,但我们需要更“完美”

JavaScript专题(三)防抖

四、防抖进阶

4.1 立即执行

这个需求就是:

  • 立即执行
  • 保持n秒空白期
  • n秒空白期置后

想想这个需求也是很有道理的嘛,那我们加个immediate参数判断是否是立刻执行。

function debounce(func, wait, immediate) { 
    var timer; return function () { 
    var _this = this; var args = arguments; if (timer) clearTimeout(timer); // 常规流程,间隔内触发时清掉重置定时 if (immediate) { 
    // 如果已经执行过,不再执行 var callNow = !timer; // 1. callNow 初始值是 true, 同步立即执行;随后 timer 才开始执行 timer = setTimeout(function(){ 
    timer = null; // wait 期间,timer 是一个 ID 数字,所以 callNow 为 false,func 在此期间永远不会执行 }, wait) // wait 之后,timer 赋值 null,callNow 为 true,func 又开始立即执行。 if (callNow) func.apply(_this, args) } else { 
    timer = setTimeout(function(){ 
    func.apply(_this, args) }, wait); } } } 

再来看下此时他是什么效果:

在这里插入图片描述

4.2 添加简单验证
function debounce(func, wait, immediate) { 
    var timer; // 检查函数 if (typeof func !== 'function') { 
    throw new TypeError('Expected a function'); } // 保证wait存在 wait = +wait || 0; const debounced = function () { 
    var _this = this; var args = arguments; if (timer) clearTimeout(timer); // 常规流程,间隔内触发时清掉重置定时 if (immediate) { 
    // 如果已经执行过,不再执行 var callNow = !timer; // 如果不存在定时器,则callNow为true timer = setTimeout(function () { 
    timer = null; // 为了保证之后的时效性,手动添加timer }, wait) // 因为不存在timer,证明是首次执行,所以直接调用 if (callNow) func.apply(_this, args) } else { 
    timer = setTimeout(function () { 
    func.apply(_this, args) }, wait); } } return debounced } 
4.3 添加取消事件方法

如果你希望能取消被防抖的事件,我们可以这样写:

function debounce(func, wait, immediate) { 
    var timer; // 检查函数 if (typeof func !== 'function') { 
    throw new TypeError('Expected a function'); } // 保证wait存在 wait = +wait || 0; const debounced = function () { 
    var _this = this; var args = arguments; if (timer) clearTimeout(timer); // 常规流程,间隔内触发时清掉重置定时 if (immediate) { 
    // 如果已经执行过,不再执行 var callNow = !timer; // 如果不存在定时器,则callNow为true timer = setTimeout(function () { 
    timer = null; // 为了保证之后的时效性,手动添加timer }, wait) // 因为不存在timer,证明是首次执行,所以直接调用 if (callNow) func.apply(_this, args) } else { 
    timer = setTimeout(function () { 
    func.apply(_this, args) }, wait); } } const cancel = function(){ 
    clearTimeout(timer); timer = null; } const pending = function(){ 
    return timer !== undefined; } debounced.cancel = cancel; debounced.pending = pending; return debounced } 

我们再来看看效果:

在这里插入图片描述

写到这里这个简单的防抖方法就算OK了,它确实还不算完美,如果在改进上有任何建议,不妨在评论区留言吧~

参考

  • 冴羽大佬的Js系列
  • lodash.js

写在最后

JavaScript系列:

  1. 《JavaScript内功进阶系列》(已完结)
  2. 《JavaScript专项系列》(持续更新)

关于我

  • 花名:余光(沉迷JS,虚心学习中)
  • WX:j

其他沉淀

  • Js版LeetCode题解
  • 前端进阶笔记
  • 我的CSDN博客

如果您看到了最后,对文章有任何建议,都可以在评论区留言~

如果真的对您有所帮助,也希望您能本系列的GitHub仓库,传送门点个star,这是对我最大的鼓励 !

JavaScript专题(三)防抖

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/204618.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月19日 下午7:55
下一篇 2026年3月19日 下午7:55


相关推荐

  • rocketmq的原理_dns原理及其解析过程

    rocketmq的原理_dns原理及其解析过程1如何保证消息的可靠性传输生产者丢失数据:生产者设置同步提交消息,并且手动提交,将消息同步刷盘到从节点后在返回成功,broker:主从复制,同步刷盘消费端:消费重试,只有返回consume_success才算消费完成,保证消息的可靠性,最终还是消费16次还是失败的会进死信队列2.如何保证消息不被重复消费消费端消费消息的幂等1服务端代码根据messageId设置分布式锁,获取锁再做业务操作2更新数据库时校验业务的状态3或者设置唯一索引3.如何保证消息的顺序性…

    2025年6月27日
    3
  • ConcurrentSkipListMap api详解

    ConcurrentSkipListMap api详解今天时间学习 ConcurrentSk 该类是 JUC 原子包中的类 通过单元测试代码把所有 publicapi 方法跑了一遍 大致了解了底层实现 初学乍练 有很多一知半解的地方 待后续有了深入理解再来补充 packagetest java util concurrent importjava util importjava util concurrent ConcurrentNa importjava util concurrent C

    2026年3月19日
    2
  • 红旗 Linux 官方社区_centos桌面图标

    红旗 Linux 官方社区_centos桌面图标参考资料下载:http://www.ctdisk.com/u/665442 计算机和网络技术不断推进着人类的生产力,Linux的诞生又为之带来一场开放与自由的变革。现在,作为亚洲最大、发展最迅速的Linux产品发行商,北京中科红旗软件技术有限公司(红旗软件)引领着这场变革的方向,通过提供高品质的产品和服务,开创全新的计算体验,帮助企业增强其整体竞争力,提高个人工作效率,将用户业务价值和Li

    2022年8月21日
    14
  • JavaWeb专栏之(四):什么是servlet(底层源码及执行流分析)?[通俗易懂]

    JavaWeb专栏之(四):什么是servlet(底层源码及执行流分析)?[通俗易懂]JavaWeb专栏之(四):什么是servlet(底层源码及执行流分析)?前言:本文探究JavaWeb中Servlet的源码及执行流过程,相信您看完后,为金三银四的面试中如虎添翼。俗话知其然,知其所以然。底层代码的理解擦才是YYDS,让小Du猿带您一起走进Servlet的底层实现源码吧</p>最后:本专栏的代码已经同步到Gitee中,欢迎小伙伴一键start,原创整理不易,多多支持哦!传送门:https://gitee.com/shunchangdu/Javaweb_se

    2022年6月18日
    30
  • jmeter-header获取ssionid

    jmeter-header获取ssionid

    2021年9月18日
    77
  • js的promise用法

    js的promise用法js 的 promise

    2026年3月18日
    1

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号