JavaScript 中的闭包、防抖与节流:让你彻底搞懂它们的作用和应用场景

引言

在前端开发中,闭包(Closure)防抖(Debounce)节流(Throttle) 是非常常见的概念。它们不仅在日常开发中经常用到,也是面试中的高频考点。那我们就来一个个了解。


首先,什么是闭包

当内部函数被传递到它所在的词法作用域之外,内部函数会保存对原始作用域的引用,这个引用就叫做闭包,无论在何处执行这个函数,都会使用闭包。一个函数能够访问并记住其词法作用域,即使该函数在其作用域外执行。

也可以说,函数嵌套函数 ,外部访问的时候,可以沿着我们的词法作用域链找到它声明的时候的函数中的变量函数就好像有一个背包一样,里面放着外层函数的变量

下面举zsf的求爱之旅被截断的例子:

javascript 复制代码
function findlove() {
    var name=("zsf");
    let count = 0;
        var a = {
       getlove:function(){
        if(name=="zz"){
            console.log("zz找到真爱了")
            console.log(count);
            return
        }
        count++
       console.log(name+"爱婷婷"+","+"被拒绝次数:"+count)
       },
       changename:function(new_name){
       name=new_name
       }
        }
    return a
    }
    var thing = findlove()
    thing.getlove()
    thing.getlove()
    thing.changename("zz")
    thing.getlove()

输出结果为

由此可见,getlove是在findlove内部的一个函数,可当它在外部作用域被调用时,name的值和count的值都被记住了,- 这就是闭包的核心能力:内部函数能"记住"外部函数中的变量,并且在执行其他函数之后,getlove的值也没有被销毁

这就是闭包


其次,引入函数防抖(Debounce)的概念

在事件被触发后,等待一段时间后它没有再次被触发,才真正执行。

通俗一点的理解就是,打游戏时点击回城按钮,在回城这段期间,如果再次点击回城按钮,回城这个事件就会重新执行,就得从头回,可如果超过了回城所需的时间,说明回城这个事件已经结束了,再次点击就相当于第二次触发这个事件了,这就是函数防抖。

就像你在打字搜索框输入内容时,不是每次按键都发请求,而是等你不打了再发。

使用场景:
  • 输入框搜索联想
  • 窗口大小调整
  • 频繁触发的事件(如滚动、鼠标移动)

下面是debounce函数的使用:

javascript 复制代码
function debounce(fn, delay) {
  let timer; // 定时器变量

  return function(...args) {
    clearTimeout(timer); // 清除之前的定时器
    timer = setTimeout(() => {
      fn.apply(this, args); // 执行目标函数
    }, delay);
  };
}

这里的fn就是要防抖的事件,delay就是事件完成的时间

或者说,fn就是回城,delay就是回城要用的时间

举个例子:

xml 复制代码
<body>
    <input type="text" id="inputB">
    <br>
    <br>
    <script>
        let inputB = document.getElementById('inputB');
        function ajax(content){
            console.log('ajax request'+ content);
        }
        //函数的参数也是函数,高阶函数
        //通用函数,抽象,fn任何函数减少执行频率
        function debounce (fn,delay){
          //
          return function (args){
            //定时器会返回一个ID
                clearTimeout(fn.id);
            fn.id = setTimeout(function(){
                fn(args);
            },delay)
          }
        }

        //高阶函数 耗时函数-> 闭包的自由变量
        //返回一个新函数 频繁执行,
        let debounceAjax = debounce(ajax,200);
        inputB.addEventListener('keyup', debounce(function(event){
    ajax(event.target.value);
}, 200));
    </script>

接着,focus一下节流(Throttle)

在一定时间间隔内只允许函数执行一次。

比如页面滚动时,每隔 500ms 检查一次位置,而不是每次滚动都检查。也可以理解成玩游戏时技能冷却,一定时间内只能触发一次,在冷却时间内,你再怎么点击都不会触发了。

下面是throttle函数的使用:

ini 复制代码
function throttle(fn, delay) {
  let last = 0;
  return function(...args) {
    const now = Date.now();
    if (now - last > delay) {
      fn.apply(this, args);
      last = now;
    }
  };
  • function throttle(fn, delay) :定义 throttle 函数,接收两个参数, fn 是需要节流的原函数, delay 是时间间隔。
  • let last = 0; :初始化 last 变量,用于记录上一次执行原函数的时间戳。
  • return function(...args) :返回一个新函数,使用剩余参数语法 ...args 接收任意数量的参数。
  • const now = Date.now(); :获取当前时间戳。
  • if (now - last > delay) :判断当前时间与上一次执行时间的差值是否大于设定的延迟时间。
  • fn.apply(this, args); :如果满足条件,则调用原函数,并将 this 上下文和参数传递过去。
  • last = now; :更新 last 为当前时间,记录本次执行时间。
使用场景

节流函数常用于处理频繁触发的事件,如滚动事件、窗口大小调整事件等,通过限制函数的执行频率,可以提高性能,避免不必要的计算。


最后,学会用闭包实现类的封装(模拟私有变量)

在 JavaScript 中,没有像其他语言(如 Java、C++)那样的严格意义上的私有属性和方法。但是,我们可以利用闭包的特性来模拟这种行为,使得某些变量只能在特定的作用域内访问,而不能从外部直接访问。

ini 复制代码
function createCounter() {
  let count = 0;

  return {
    increment() {
      count++;
    },
    getCount() {
      return count;
    }
  };
}

const counter = createCounter();
counter.increment();
console.log(counter.getCount()); 
console.log(counter.count);

输出:

这里的const就是函数内部的变量,在外部无法访问,只能利用调用里面的函数来实现用内部变量完成操作,若直接调用,则该变量作为一个"undefined"无法使用。


总结

闭包 是 JavaScript 中的重要机制,它允许函数访问并记住其词法作用域,即使该函数在其作用域外执行。闭包常用于实现私有变量、柯里化、记忆函数、偏函数等高级功能,是封装类和模块化的基础。

防抖(debounce) 的核心在于:在频繁触发某事件时,只有在最后一次触发后的一段时间内没有再次触发,才会真正执行函数。适用于搜索框输入、窗口调整等场景。

节流(throttle) 的关键在于:确保函数在一定时间间隔内只执行一次,避免高频事件过多触发造成性能问题。常用于滚动监听、resize 事件等。

闭包是实现防抖和节流的关键基础,通过闭包可以保存定时器状态或上一次执行时间,从而实现对函数执行频率的精确控制。

相关推荐
姑苏洛言19 分钟前
扫码点餐小程序产品需求分析与功能梳理
前端·javascript·后端
Freedom风间23 分钟前
前端必学-完美组件封装原则
前端·javascript·设计模式
江城开朗的豌豆28 分钟前
React表单控制秘籍:受控组件这样玩就对了!
前端·javascript·react.js
一枚前端小能手44 分钟前
📋 代码片段管理大师 - 5个让你的代码复用率翻倍的管理技巧
前端·javascript
国家不保护废物1 小时前
Web Worker 多线程魔法:告别卡顿,轻松实现图片压缩!😎
前端·javascript·面试
陈陈陈同学241 小时前
Vercel迁移到Dokploy自部署,每月立省20刀
后端·node.js
接着奏乐接着舞。1 小时前
如何在Vue中使用拓扑图功能
前端·javascript·vue.js
阳先森2 小时前
Vue3 Proxy 为何不直接返回target[key],选用Reflect
前端·vue.js
ONE_Gua2 小时前
魔改chromium源码——解除 iframe 的同源策略
前端·后端·浏览器
用户1512905452202 小时前
mysql8的collate问题和修改
前端