- 作者:老汪软件技巧
- 发表时间: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 一样吗 ?
我们知道当我们定义一个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'。
从防抖函数总结闭包的特性闭包的特性
最后有一个问题,如果一个包装函数没有任何需要保存的值,也没有形成对外部作用域的变量引用,那么它的闭包环境会不会被持续保存
答案是不会,其实是会被内存回收掉,这就是闭包形成的要点
也就是说 如果函数内部没有引用外部变量,它不会形成闭包,函数执行完毕后外部作用域的变量会被垃圾回收,函数的上下文环境不会被保存。这种情况下,封装好的函数也不会保存当时的上下文环境,只会执行其自身的逻辑。