头图

防抖的基础实现

先来看一段防抖代码

let dom = document.querySelector('#btn')

dom.addEventListener('click',function(){
    setShake();
})

function onClick() {
    let i = null;
    // 这里也可以用箭头函数,以获取外部的this
    return function() {
      if(i != null) clearTimeout(i)
      i = setTimeout(() => {
       console.log('执行防抖')
      },1000)
    }
}

let setShake = onClick();

在这里,我用了一个闭包让 i 这个变量变为私有存储,然后通过setShake保存闭包函数onClick,调用setShake函数就会产生防抖效果,在1000秒内连续触发则只会执行一次。

在这里有一个问题,问什么一定要用setShake保存onClick()函数,而不是直接调用onClick()函数?
例如:

dom.addEventListener('click',function(){
    onClick(); // 闭包失效
})

在这段代码中,使用 let setShake = onClick(); 的目的是为了将 setShake 变量设置为闭包函数的返回值,也就是一个新的函数。这是因为闭包函数 onClick 返回了另一个函数,并且在返回的函数中使用了闭包中保存的局部变量 i
如果直接调用 onClick(),由于每次调用都会生成一个新的闭包环境,旧的计时器变量 i 不会被保留,因此无法达到防抖的效果。而将 onClick() 的返回值保存在 setShake 变量中,就可以保留闭包中的状态,并在每次调用 setShake 时共享同一个闭包环境,从而实现了防抖效果。
因此,需要将闭包函数的返回值赋给一个变量,才能正确地保持闭包中的状态,并实现预期的功能。

简单理解就是js内存地址的问题,关于引用类型的存储问题:
JavaScript 中,基本数据类型(如数字、字符串等)是按值传递的,而引用类型(如对象、函数等)是按引用传递的。当你在 JavaScript 中创建一个对象或函数时,实际上是在内存中分配了一个引用,而不是直接存储对象本身的数据。当你将一个引用赋给另一个变量时,这两个变量都指向同一个内存地址,因此它们共享相同的数据。
在这个例子中,let setShake = onClick(); 会将闭包函数的返回值(另一个函数)存储在 setShake 变量中。因为闭包中的变量 i 是作用域内的变量,当你调用 setShake 函数时,它实际上共享了相同的闭包环境,因此能够正确地保持 i 的状态并实现防抖效果。
而如果直接调用 onClick(),每次调用都会创建一个新的闭包环境,闭包中的变量 i 也会被重新初始化,无法实现预期的防抖行为。
因此,理解 JavaScript 中的引用类型和内存地址的工作原理对于编写正确的代码非常重要。

通俗来讲:
如果直接调用onClick(),那么每次都会产生一个新的作用域,i 变量就会被重新实初始化;
如果通过let setShake = onClick()保存,闭包,那么setShake就会生成一个作用域,每次调用setShake地址都只会指向同一个引用,防抖才会正确执行。

防抖立即触发

通过添加isFirst表示来实现防抖立即触发,如果第二次触发,会走防抖,直到防抖结束,恢复初始状态

let isFirst = ref(true); // 添加标识
function onShake() {
  let i = null
  return function () {
    if (isFirst.value) {
      // ...其它操作
      isFirst.value = false
    } else {
      if (i != null) clearTimeout(i)
      i = setTimeout(() => {
        isFirst.value = true
      }, 1000)
    }
  }
}
const onAntiShake = onShake();

闭包的必要性-优化

1、闭包可以将变量持久化存储,可以不被垃圾回收机制回收。
2、根据函数作用域的定义,函数内部可以访问函数外部的变量,而外部无法访问内部的变量,所以闭包可以实现数据私有化。我们可以写一个自调用函数,只允许闭包内部函数访问该变量。这么做的必要性是因为,如果我们通过模块引入js文件,我们无法知道其它模块是否有同名变量,所以我们可以通过闭包实现变量私有化,避免命名冲突。

(function(){
    let data = '变量'

    function method() {
        console.log(data)
    }

    window.method = {
        method,
    }
})()

window.method.method(); // 变量

兔子先森
485 声望559 粉丝

致力于新技术的推广与优秀技术的普及。