前端经典练习: 2. 闭包

31 阅读4分钟

闭包的定义:

​ 闭包是指有权访问另一个函数作用域中变量的函数

闭包的形成:

​ 当内部函数被返回并在外部作用域中调用时,闭包正式形成。此时外部函数已执行完毕,但内部函数仍能访问其变量

闭包demo:

function outer() {
  let count = 0; // 外部函数的变量
  function inner() {
    count++; // 内部函数(闭包)访问外部变量
    console.log(count);
  }
  return inner; // 返回闭包
}

const closure = outer(); // outer执行完毕,但count被闭包“记住”
closure(); // 输出 1
closure(); // 输出 2

闭包使用场景:

1. 数据封装与私有变量

闭包可以模拟面向对象编程中的私有变量,避免全局污染,实现数据封装

示例:计数器模块

const counterModule = (function() {
  let count = 0; // 私有变量

  return {
    increment: function() {
      count++;
      return count;
    },
    decrement: function() {
      count--;
      return count;
    },
    getCount: function() {
      return count;
    }
  };
})();

console.log(counterModule.increment()); // 1
console.log(counterModule.increment()); // 2
console.log(counterModule.decrement()); // 1
console.log(counterModule.getCount());  // 1

关键点

  • 通过 IIFE(立即执行函数)创建一个闭包,count 变量被内部函数引用,外部无法直接修改。
  • 对外暴露的 incrementdecrementgetCount 方法可以操作 count,但无法直接访问它。

2. 函数工厂(动态生成函数)

闭包可以根据输入参数动态生成具有特定行为的函数。

示例:生成乘法函数

function createMultiplier(multiplier) {
  return function(x) {
    return x * multiplier; // 闭包记住了 multiplier 的值
  };
}

const double = createMultiplier(2); // 生成一个“乘以2”的函数
const triple = createMultiplier(3); // 生成一个“乘以3”的函数

console.log(double(5)); // 10
console.log(triple(5)); // 15

关键点

  • createMultiplier 返回的函数是一个闭包,它记住了传入的 multiplier 参数。
  • 每次调用 createMultiplier 会生成一个新的闭包,拥有独立的 multiplier 值。

3. 事件处理与回调

  • 场景:在异步回调中访问外部变量。

  • 示例

    function setupButtons() {
      const buttons = document.querySelectorAll('button');
      buttons.forEach((button, i) => {
        button.addEventListener('click', () => {
          console.log(`Button ${i} clicked`); // 闭包记住i的值
        });
      });
    }
    
  • 作用:避免循环中因变量共享导致的错误(如 i 始终为最终值)。


4. 防抖(Debounce)与节流(Throttle)

  • 场景:控制高频事件的触发频率。

  • 示例(防抖)

    function debounce(fn, delay) {
      let timer;
      return (...args) => {
        clearTimeout(timer);
        timer = setTimeout(() => fn(...args), delay); // 闭包保留timer
      };
    }
    const handleResize = debounce(() => console.log('Resized'), 200);
    window.addEventListener('resize', handleResize);
    
  • 作用:闭包保留定时器变量,确保延迟期间重复调用被忽略。


5. 模块模式(Module Pattern)

  • 场景:封装模块,暴露有限接口。

  • 示例

    const module = (() => {
      let privateVar = 'secret';
      function privateMethod() { return privateVar; }
      return {
        publicMethod: () => privateMethod() // 通过闭包访问私有成员
      };
    })();
    console.log(module.publicMethod()); // 'secret'
    
  • 作用:利用闭包实现模块化,隐藏内部实现细节。


6. 记忆化(Memoization)缓存

  • 场景:缓存函数计算结果,提升性能。

  • 示例

    function memoize(fn) {
      const cache = new Map();
      return (...args) => {
        const key = JSON.stringify(args);
        if (cache.has(key)) return cache.get(key);
        const result = fn(...args);
        cache.set(key, result); // 闭包保留cache
        return result;
      };
    }
    
  • 作用:闭包保存缓存对象,避免重复计算。


7. 柯里化(Currying)

  • 场景:将多参数函数转换为单参数函数链。

  • 示例

    function curry(fn) {
        return function curried(...args) {
          if (args.length >= fn.length) {
            return fn(...args);
          } else {
            return function(...moreArgs) {
              return curried(...args, ...moreArgs);
            };
          }
        };
      }
      function sum(a, b, c) { return a + b + c; }
      const curriedSum = curry(sum);
      console.log(curriedSum(1)(2)(3)); // 6
    
  • 作用:闭包逐步接收参数,实现灵活的函数调用。

闭包缺点及解决方式:

1. 内存消耗

  • 变量无法释放:闭包会长期持有外部变量的引用,即使外部函数已执行完毕,这些变量仍会保留在内存中,可能导致内存泄漏。

    function heavyFunction() {
      const hugeData = new Array(1000000).fill("data");
      return () => console.log(hugeData[0]); // 闭包保留hugeData
    }
    const closure = heavyFunction();
    // 即使不再需要closure,hugeData仍占用内存
    
  • 解决方案:手动解除引用(如 closure = null)。

2. 性能开销

  • 作用域链查找:闭包访问外部变量时需要沿着作用域链逐级查找,比访问局部变量稍慢(但现代引擎优化后影响较小)。
  • 创建开销:频繁创建闭包可能增加内存分配和垃圾回收的负担

3. 共享变量导致的意外行为

  • 闭包共享外部变量:多个闭包实例可能共享同一外部变量,导致状态被意外修改。

4. 调试复杂性

  • 作用域链隐蔽性:闭包的作用域链可能使调试时难以追踪变量的来源,尤其是嵌套较深时。

5. 过度使用导致代码难以维护

  • 逻辑分散:过度依赖闭包可能导致代码逻辑分散在多个作用域中,降低可读性。