手写防抖,你也应该学会。this?闭包?

前言

JavaScript 开发中,防抖是一种常用的技术手段,它能有效地优化性能和提升用户体验。

防抖的核心思想: 在短时间内连续触发事件时,只执行最后一次或几次事件处理函数,而不是立即响应每一次触发。

举个例子: 想象一下,当用户频繁操作时,如快速滚动、连续输入等,如果每次操作都立即执行相应的函数,可能会导致不必要的计算和资源浪费。这时,防抖就派上用场了。

防抖的关键: 在于设置一个延迟时间。在触发事件后,启动一个定时器,如果在定时器到期之前再次触发事件,就会重置定时器,只有当定时器到期且没有新的触发时,才会真正执行事件处理函数。

防抖的部分用途举例: 防抖在实际应用中有广泛的用途,比如搜索框实时搜索的优化、按钮连续点击的处理等。

手写防抖案例

这里还是直接步入正题,手写防抖,写完之后大家应该就能够更好地理解什么是防抖防抖有什么作用怎么手写防抖

js 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>debounce</title>
</head>
<body>
    <button id="btn">提交</button>
    <script>
        let btn = document.getElementById('btn');

        function handle() {
            // ajax请求
            console.log('提交');
        }
        btn.addEventListener('click', handle);
  
    </script>
</body>
</html>
  • 首先大家来看看这份代码,很简单,就是一个button按钮,然后为这个按钮绑定了一个监听器,当点击这个button按钮时,就会触发这个监听器调用这个回调函数,这个回调函数直接封装在外面了,函数内容就是打印一个提交。
  • 那么大家思考一下,如果用户一直点击这个按钮,控制台是不是就会一直输出提交?试想一下,如果是一个大型项目,有很多人同时在线这么干,咱们的服务器岂不是一下就崩掉了?
  • 接下来我们就带着这个问题来思考一下,如果解决?(防抖来了。)
js 复制代码
 // 防抖函数
        function debounce(fn) {
           
            return function() {
             setTimeout(()=>{
                fn();
               }, 1000);
        }
     }

既然我们要做防抖,那我们不妨就直接写一个防抖函数,这个函数有一个返回值,返回值是一个函数,在此我们姑且称之为子函数。

  • 那么为什么需要这个子函数呢?
  • 我们刚刚是给button按钮绑定了一个监听器,这个监听器是需要一个回调函数的,如果我们这里没有返回一个函数,而是直接把这个防抖函数的调用丢进去,那就会直接报错了。
js 复制代码
btn.addEventListener('click', debounce(handle));

现在我们继续思考,是不是点一下button按钮,这个防抖函数就会被调用一次,调用一次就会返回一个子函数,然后子函数被调用,子函数就会在一秒之后调用fn函数,fn函数就是打印提交。那还是没解决问题,他只是延迟了一秒钟,但是服务器该响应我们的提交还是要相应提交。

因此接下来我们这么干,声明一个timer,首先他是空的,然后传给子函数里面,在子函数里面我们做删除定时器的操作,只要把定时器删掉了,这个fn函数就不会被调用了

js 复制代码
 function debounce(fn) {
            let timer = null;
           
            return function() {
             // 如果第二次的时间没到1s,就销毁上一次的定时器
            clearTimeout(timer);
            timer = setTimeout(()=>{ 
                fn();
               }, 1000);
            }
        }

看看目前的代码,现在的情况是不是,我们用户如果一秒钟都还没到,就又点了一下提交,那子函数就会删除这个定时器一秒钟之后才去执行这个事情,那么在1秒钟之内,如果你疯狂发送请求,服务器也不需要搭理你了。

思考 (闭包)

这个地方是怎么做到的?运用了什么原理? 回想一下之前写的文章,这里其实就是一个函数,里面又有一个函数,但是这个里面的子函数,父级函数管不到它了,它的调用完全是归于外界管理,你要说父函数执行完毕没有,他确实是执行完毕了,既然执行完毕了就应该把它的执行上下文对象给销毁掉,但是js引擎考虑到它的子函数还要用timer这个变量,如果直接把执行上下文销毁掉了,这个子函数就无法访问到这个变量了,于是他就留下了一个小背包,这个小背包就是所谓的闭包

至此,从表面上看,我们要实现的防抖它的确是实现了,可是,这就完成了防抖吗?

js 复制代码
 <script>
        let btn = document.getElementById('btn');

        function handle() {
            // ajax请求
           console.log('提交', this);
        }
         btn.addEventListener('click', handle);// this 指向btn

        // 防抖函数
        function debounce(fn) {
            let timer = null;
           
            return function() {
             // 如果第二次的时间没到1s,就销毁上一次的定时器
            clearTimeout(timer);
            timer = setTimeout(function() { 
                fn();
               }, 1000);
            }
        }
    </script>

大家来看看这份代码,在我们没有给程序做防抖之前,这里的this指向谁?我们做完防抖之后,会不会改变这个this的指向

  • 在这份代码中,这里的this指向btn,看this指向哪,我之前有一篇文章讲过,这里要看这个函数是什么绑定方式,这里的handle函数是由监听器绑定的,由监听器触发调用,因此它属于this中的隐式绑定,因此这里的this应该指向btn
  • 但是在我们设置防抖之后,现在可以回去看看刚刚做了防抖的代码,那里的this事实上是指向window的,handle函数的调用是在定时器内的回调函数中调用的,定时器的回调函数指向window,因此在我们做了防抖以后这个this会指向window。
  • 既然如此,我们虽然帮助别人把防抖搞定了,相当于搞定了一个大问题,但是带了了一堆的小问题,在大项目中,更改了这些this指向...等等问题很有可能会带来很多的bug。
  • 因此我们如何把这个this指向给他掰回去?其实之前写的this文章中也有讲到如何掰弯this指向(显式绑定的方式)call、apply、bind三种方法
js 复制代码
// 防抖函数
        function debounce(fn) {
            let timer = null;
           
            return function(e) {
             // 如果第二次的时间没到1s,就销毁上一次的定时器
            clearTimeout(timer);
            timer = setTimeout(()=>{ 
               fn.call(this);
               }, 1000);

            }
        }
  • 在这里我们使用了箭头函数,而箭头函数是没有this这个机制的,它里面的fn中的this会去找它的外层非箭头函数的(子函数)的this,而这个子函数又是由btn调用的,通过这样一个操作,这个this的指向就成功被掰回去了。
  • 这里大家有没有发现,子函数中还传递了一个参数e
js 复制代码
 <script>
        let btn = document.getElementById('btn');

        function handle(e) {
            // ajax请求
            console.log('提交', this, e);
        }
        btn.addEventListener('click', handle);// this 指向btn
        
        // 防抖函数
        function debounce(fn) {
            let timer = null;
          
            return function(e) {
                // 如果第二次的时间没到1s,就销毁上一次的定时器
            clearTimeout(timer);
            timer = setTimeout(()=>{ 
                fn.call(this, e);
               }, 1000);
            }
        }
    </script>

大家看看在我们没做防抖的时候,这个函数的默认参数e,里面是什么

但是在我们设置了防抖以后,这个函数的默认参数e,就变成了undefined。

  1. 为什么变成了undefined
  2. 怎么把这个bug给它修正
js 复制代码
 // 防抖函数
        function debounce(fn) {
            let timer = null;
          
            return function(e) {
                // 如果第二次的时间没到1s,就销毁上一次的定时器
            clearTimeout(timer);
            timer = setTimeout(()=>{ 
                fn.call(this, e);
               }, 1000);
              }
        }
  • 通过传参,把函数上的e给他传回去
  • 回头想一下,我们掰弯this的时候,是不是把this给传进去,this这个关键字在这里传递,大家会不会看着很别扭?因此在这里我们可以自己设置一个变量例如that,把that给传进去
js 复制代码
  // 防抖函数
        function debounce(fn) {
            let timer = null;
         
            return function(e) {
                const that = this;
                
                // 如果第二次的时间没到1s,就销毁上一次的定时器
               clearTimeout(timer);
               timer = setTimeout(function() {
                fn.call(that, e);
               }, 1000);
               
            }
        }

如果改成这样,是不是看上去就舒服很多了。

小结

  1. 防抖是一种优化性能和提升用户体验的技术手段,其核心思想是在短时间内连续触发事件时,只执行最后一次或几次事件处理函数。
  2. 防抖的关键在于设置一个延迟时间,在触发事件后启动一个定时器,如果在定时器到期之前再次触发事件,就会重置定时器,只有当定时器到期且没有新的触发时,才会真正执行事件处理函数。
  3. 防抖在实际应用中有广泛的用途,如搜索框实时搜索的优化、按钮连续点击的处理等。
  4. 通过一个简单的示例演示了如何手写防抖函数,并详细介绍了防抖函数的实现过程,包括如何设置延迟时间、如何处理连续触发事件、如何避免 this 指向问题以及如何解决参数传递问题。
相关推荐
杰哥在此21 分钟前
Python知识点:如何使用Multiprocessing进行并行任务管理
linux·开发语言·python·面试·编程
汪子熙22 分钟前
Angular 服务器端应用 ng-state tag 的作用介绍
前端·javascript·angular.js
Envyᥫᩣ30 分钟前
《ASP.NET Web Forms 实现视频点赞功能的完整示例》
前端·asp.net·音视频·视频点赞
Мартин.4 小时前
[Meachines] [Easy] Sea WonderCMS-XSS-RCE+System Monitor 命令注入
前端·xss
昨天;明天。今天。6 小时前
案例-表白墙简单实现
前端·javascript·css
数云界6 小时前
如何在 DAX 中计算多个周期的移动平均线
java·服务器·前端
风清扬_jd6 小时前
Chromium 如何定义一个chrome.settingsPrivate接口给前端调用c++
前端·c++·chrome
安冬的码畜日常6 小时前
【玩转 JS 函数式编程_006】2.2 小试牛刀:用函数式编程(FP)实现事件只触发一次
开发语言·前端·javascript·函数式编程·tdd·fp·jasmine
ChinaDragonDreamer6 小时前
Vite:为什么选 Vite
前端
小御姐@stella6 小时前
Vue 之组件插槽Slot用法(组件间通信一种方式)
前端·javascript·vue.js