• 作者:老汪软件技巧
  • 发表时间:2024-10-04 15:01
  • 浏览量:

防抖函数理解闭包

防抖函数(Debounce)是一个非常经典的闭包应用案例。它通过闭包保存了函数的上下文和计时器,来实现在一段时间内频繁触发时,只执行最后一次操作的功能。

下面先看一下防抖函数的一个简单实现

 function debounce(func, wait) {
     let timeout;  // timeout 是闭包中的变量
 ​
     return function(...args) {
         const context = this;
 ​
         // 每次调用清除前一个定时器
         clearTimeout(timeout);
 ​
         // 设置一个新的定时器
         timeout = setTimeout(() => {
             func.apply(context, args);  // 在 wait 时间后执行
         }, wait);
     };
 }

下面有几个问题

1.防抖函数实际上是包装了传入的函数,return出去了这个包装好的函数,那我们接受到的不就只是这个函数吗?

但事实上是,因为包装的函数中存在一个作用域,这个作用域里面的变量还一直在被引用,所以会一直存在,虽然返回的是这个函数,但它的外层作用域(包括内部的 timeout 变量)被闭包保存了下来

2.防抖函数是怎么每次获取到上一次定时器的ID,进行清除的呢?

正如上面所说,它每次都可以访问到外层作用域中保存下来的变量

3.那我们能不能手动获取这个timeout呢?

答案肯定是不行的,因为它就没有暴露出来,所以这又是闭包的又一好处,无法被外部直接访问

引入一个新的问题

相信你已经理解了上面的问题,那么下面的问题将加深闭包的理解,如果我用debounce()包装了一个函数,拿到了这个函数的返回值(包装好的函数),我们知道这个包装好的函数可以接受值 ,那么假如说

const package = debounce(cellback,1000);

const clickOne = package(arg1);

const chickTwo = package(arg2);

那么它们两个访问到的timeout 一样吗 ?

防抖是闭包吗_防抖函数js_

我们知道当我们定义一个add函数,每次调用 add 函数时,如果传入不同的 args,那么每次生成的函数执行上下文环境(即 this 以及函数内的变量和参数)是不同的。这是因为每次函数调用都会有独立的执行上下文,因此函数内部的变量和参数在不同的调用之间不会相互影响。

但是牢记上面所学到的闭包,虽然他们的上下文环境,是不一样的,但是其都在package创建的作用域下,他们将共享package 创建的“外层”作用域,当然里面传入的args不会被影响

下面给出代码

function debounce(func, wait) {
    let timeout;  // 这个变量是共享的
    return function (...args) {
        const context = this;
        clearTimeout(timeout);  // 清除前一次的定时器
        timeout = setTimeout(() => {  // 设置新的定时器
            func.apply(context, args);
        }, wait);
    };
}
const d1 = debounce((...args) => console.log('Callback executed with:', ...args), 1000);
const c1 = d1('args1');  // 第一次调用,启动了一个定时器
const c2 = d1('args2');  // 第二次调用,会清除第一次调用的定时器,并启动一个新的定时器

看运行结果

下面给出了 Callback executed with:args2

第一次的呢,显然被第二次调用清除了

解决方案:独立闭包

如果需要不同的操作之间互不干扰,可以为每个操作创建独立的防抖函数实例。每个实例有独立的 timeout 变量,不会相互影响。这是通过为每个操作创建独立的闭包来实现的。

独立闭包示例:

 const d1 = debounce(() => console.log('Executed 1'), 1000);
 const d2 = debounce(() => console.log('Executed 2'), 1000);
 ​
 d1(); // 第一次调用 d1,启动 d1 的计时器
 d2(); // 第一次调用 d2,启动 d2 的计时器,互不干扰

结果:两个不同的防抖函数实例各自执行,输出 'Executed 1' 和 'Executed 2'。

从防抖函数总结闭包的特性闭包的特性

最后有一个问题,如果一个包装函数没有任何需要保存的值,也没有形成对外部作用域的变量引用,那么它的闭包环境会不会被持续保存

答案是不会,其实是会被内存回收掉,这就是闭包形成的要点

也就是说 如果函数内部没有引用外部变量,它不会形成闭包,函数执行完毕后外部作用域的变量会被垃圾回收,函数的上下文环境不会被保存。这种情况下,封装好的函数也不会保存当时的上下文环境,只会执行其自身的逻辑。