当前位置: 首页 > article >正文

JS面试题之---解释一下什么是闭包?

  • 闭包:闭包是一个函数,它能够访问并引用其外部函数作用域中的变量,即使外部函数已经执行完毕。这种特性使得闭包可以保持对外部变量的引用,从而在函数外部也能访问和修改这些变量。
function createCounter() {  
    let count = 0; // 这是外部函数的局部变量  

    return function() { // 返回的内部函数形成了闭包  
        count += 1; // 内部函数可以访问并修改外部函数的局部变量  
        return count; // 返回当前的计数值  
    };  
}  

const counter = createCounter(); // 创建一个计数器  

console.log(counter()); // 输出: 1  
console.log(counter()); // 输出: 2  
console.log(counter()); // 输出: 3  

//这个例子展示了闭包的一个典型用途:在 JavaScript 中实现数据的私有性,通过创建函数工厂生成具有独立状态的计数器。闭包使得我们可以在内部函数中访问和操作外部函数的变量,即使外部函数已经返回并结束。
  • 闭包需要满足三个条件:
    1. 函数嵌套:闭包必须是一个内部函数,即一个函数内部定义了另一个函数。
    2. 引用外部函数变量:内部函数可以访问和修改外部函数的变量。
    3. 延续外部函数的生命周期:即使外部函数已经执行完毕,内部函数仍然可以访问外部函数的变量。
function outerFunction() {  
    let outerVariable = 'I am from the outer function!'; // 外部函数的局部变量  

    // 条件1: 函数嵌套  
    function innerFunction() {  
        // 条件2: 访问外部变量  
        console.log(outerVariable);  
    }  

    return innerFunction; // 返回内部函数  
}  

const closureFunction = outerFunction(); // 执行外部函数并返回内部函数  
closureFunction(); // 输出: I am from the outer function!


//解释:
//函数嵌套:innerFunction 是定义在 outerFunction 内部的。
//访问外部变量:innerFunction 中可以访问并输出 outerVariable,即使执行 outerFunction 已经结束。
//延续外部变量的生命周期:即使 outerFunction 的执行上下文已经结束,但 innerFunction 仍然保持对 outerVariable 的引用,允许我们在调用 closureFunction() 时访问 outerVariable。
  • 优点: 可以重复使用变量,并且不会造成变量污染。
  • 缺点: 会引起内存泄漏
    • 内存泄漏是指程序在运行时没有及时释放不再使用的内存,导致可用内存逐渐减少,可能最终导致应用程序性能下降甚至崩溃。
    • 闭包可以持有外部作用域的变量引用,如果这些变量的生命周期超出了需要使用的范围,可能会导致内存泄漏。
    • 如果使用了某些长生命周期的数据结构(如单例模式或全局缓存)而没有适当地清理不再需要的数据,也可能导致内存泄漏。
      • 预防内存泄漏的方法:

        • 及时清理引用:确保不再需要的对象或变量被及时置为 null,允许垃圾回收机制回收内存。
        • function createClosure() {  
              let resource = { /* large object */ };  
          
              return function innerFunction() {  
                  // 使用 resource  
              };  
          }  
          
          // 当不再需要这个闭包,做清理  
          const closure = createClosure();  
          // ...使用 closure...  
          closure = null; // 切断与资源的引用

        • 避免使用全局变量:尽量减少全局变量的使用,使用模块化的代码组织方式。

        • let globalRef; // 避免使用,如有必要,考虑使用模块化方法。  
          
          function createClosure() {  
              let localResource = { /* large object */ };  
          
              return function innerFunction() {  
                  // 不要把 localResource 放入 globalRef  
              };  
          }
  •  闭包的主要用途包括:
    1. 实现数据封装和私有变量
      • 闭包能够通过函数作用域来封装变量,让这些变量不会被外部访问,从而实现数据封装和私有变量的效果。这有助于保护数据不被意外修改。
      • function createCounter() {  
            let count = 0; // 私有变量  
        
            return {  
                increment: function() {  
                    count++;  
                    return count;  
                },  
                decrement: function() {  
                    count--;  
                    return count;  
                },  
                getCount: function() {  
                    return count;  
                }  
            };  
        }  
        
        const counter = createCounter();  
        console.log(counter.increment()); // 1  
        console.log(counter.increment()); // 2  
        console.log(counter.getCount());  // 2
    2. 实现函数柯里化
      • 函数柯里化是将多个参数的函数转换为一系列单一参数的函数链,闭包可以用来保持对初始参数的访问。
      • function curriedSum(a) {  
            return function(b) {  
                return a + b;  
            };  
        }  
        
        const addFive = curriedSum(5);  
        console.log(addFive(10)); // 15
        
        //curriedSum 函数返回一个新函数,该新函数闭包当前的 a 值。这样你可以逐步传递参数。
    3. 实现函数防抖和节流
      • 防抖(Debounce):多次触发 只执行最后一次。
        • function debounce(func, delay) {  
              let timeout;  
              return function(...args) {  
                  clearTimeout(timeout);  
                  timeout = setTimeout(() => func.apply(this, args), delay);  
              };  
          }  
          
          const handleResize = debounce(() => {  
              console.log('Window resized!');  
          }, 200);  
          window.addEventListener('resize', handleResize);
      • 节流(Throttle):规定时间内 只触发一次。
        • function throttle(func, limit) {  
              let lastFunc;  
              let lastRan;  
              return function(...args) {  
                  if (!lastRan) {  
                      func.apply(this, args);  
                      lastRan = Date.now();  
                  } else {  
                      clearTimeout(lastFunc);  
                      lastFunc = setTimeout(() => {  
                          if ((Date.now() - lastRan) >= limit) {  
                              func.apply(this, args);  
                              lastRan = Date.now();  
                          }  
                      }, limit - (Date.now() - lastRan));  
                  }  
              };  
          }  
          
          const logScroll = throttle(() => {  
              console.log('Scroll event triggered!');  
          }, 1000);  
          window.addEventListener('scroll', logScroll);
    4. 实现缓存机制
      • 闭包可以存储和缓存计算结果,从而提高程序效率,尤其是在频繁调用某个计算开销大的函数时。
      • function memoize(fn) {  
            const cache = {};  
            return function(...args) {  
                const key = JSON.stringify(args);  
                if (cache[key]) {  
                    return cache[key]; // 从缓存返回结果  
                }  
                const result = fn.apply(this, args);  
                cache[key] = result; // 保存到缓存  
                return result;  
            };  
        }  
        
        const factorial = memoize(function(n) {  
            if (n === 0) return 1;  
            return n * factorial(n - 1);  
        });  
        
        console.log(factorial(5)); // 120  
        console.log(factorial(5)); // 从缓存返回 120
  • 使用闭包的注意点:
    1. 由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
    2. 闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象
      object )使用,把闭包当作它的公用方法( Public Method ),把内部变量当作它的私有属性 (private value ),这时一定要小心,不要随便改变父函数内部变量的值。


http://www.kler.cn/a/388742.html

相关文章:

  • NAT网络工作原理和NAT类型
  • Android音频架构
  • 在Java中使用ModelMapper简化Shapefile属性转JavaBean实战
  • 论文阅读《BEVFormer v2》
  • 基于混合配准策略的多模态医学图像配准方法研究
  • 万字长文解读深度学习——ViT、ViLT、DiT
  • 【日常经验】RPC 调用的分类及示例
  • 非关系型数据库NoSQL的类型与优缺点对比
  • API接口精准获取商品详情信息案例
  • 【前端】Svelte:响应性声明
  • 动态规划(二)——路径问题
  • Android13 系统/用户证书安装相关分析总结(三) 增加安装系统证书的接口遇到的问题和坑
  • VScode配置C、C++环境,编译并运行并调试
  • Java之List常见用法
  • VUE3实现好看的通用网站源码模板
  • 深度学习经典模型之VGGNet
  • <<零基础C++第一期,C++入门基础之引用知识点>>
  • JavaWeb--Maven
  • 系统安全第五次作业题目及答案
  • Could not create task ‘:shared_preferences_android:generateDebugUnitTestConfig‘
  • 大数据机器学习算法和计算机视觉应用01:博弈论基础
  • SpringBoot整合Sharding-JDBC实现读写分离
  • 界面控件Telerik UI for ASP.NET AJAX 2024 Q3亮点 - 新增金字塔图表类型
  • 大数据新视界 -- 大数据大厂之经典案例解析:广告公司 Impala 优化的成功之道(下)(10/30)
  • 讨论一个mysql事务问题
  • 3200. 三角形的最大高度