2023前端面试:一篇文章让你彻底搞懂闭包

47 阅读4分钟

闭包的概念

闭包是一个函数和其引用的外部变量(也称为自由变量)的组合。当函数访问外部变量时,即使该变量不在函数的作用域中,也可以在函数中使用。换句话说,闭包可以让函数访问其创建时的词法作用域中的变量

在 JavaScript 中,函数是一等公民,这意味着函数可以作为变量来使用。函数可以作为另一个函数的参数传递,也可以作为另一个函数的返回值返回。当一个函数返回另一个函数时,该函数通常会形成一个闭包。

通俗易懂的概念

当一个函数A定义在另一个函数B的内部,并且函数A使用了函数B中的变量,那么当函数B执行完毕后,函数A仍然可以访问到函数B中的变量,这种情况就被称为闭包。可以将闭包看作是一个封闭的背包,背包里面装着函数A以及函数A使用到的变量,背包可以随时打开使用里面的内容。

用javascript举一个闭包的例子

function outerFunction() {
  let outerVariable = "Hello, World!";

  function innerFunction() {
    console.log(outerVariable);
  }

  return innerFunction;
}

let myClosure = outerFunction();
myClosure(); // 输出 "Hello, World!"

在这个示例中,outerFunction 是一个包含 innerFunction 的函数。outerVariable 是在 outerFunction 中定义的变量,它在 innerFunction 中被引用。当我们在 outerFunction 中调用 innerFunction 并将其返回时,它形成了一个闭包,使得 innerFunction 可以访问 outerVariable,即使 outerFunction 已经返回并且 outerVariable 在其作用域内不可访问。

闭包在js中常见的使用场景

  1. 创建私有变量和方法:可以使用闭包来创建私有变量和方法,以确保只能通过特定的函数进行访问和修改。这在设计模式中经常被用到。
function Counter() {
  let count = 0;

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

let counter = Counter();
counter.increment();
console.log(counter.getCount()); // 输出 1

在这个示例中,Counter 函数返回一个对象,包含两个方法 incrementgetCount,它们都可以访问 count 变量,但是外部无法访问 count 变量。这样就实现了私有变量和方法。

  1. 延迟函数的执行:可以使用闭包来延迟函数的执行,例如在事件处理程序中,以便在一定时间内多次触发事件时,只执行最后一次触发的事件。
function debounce(fn, delay) {
  let timer = null;

  return function() {
    let context = this;
    let args = arguments;

    clearTimeout(timer);
    timer = setTimeout(function() {
      fn.apply(context, args);
    }, delay);
  };
}

let doSearch = debounce(function() {
  console.log('searching...');
}, 500);

doSearch();
doSearch();
doSearch();

在这个示例中,debounce 函数返回一个新函数,它会在调用时启动一个定时器,在一定时间内只执行最后一次调用,并将调用传递给原始函数 fn。这样就实现了延迟函数的执行。

  1. 模块化开发:可以使用闭包来创建模块,使得模块的变量和方法不会与全局作用域中的变量和方法发生冲突。
let myModule = (function() {
  let privateVar = 'Hello, World!';

  function privateFunc() {
    console.log(privateVar);
  }

  return {
    publicFunc: function() {
      privateFunc();
    }
  };
})();

myModule.publicFunc(); // 输出 "Hello, World!"

在这个示例中,myModule 对象通过一个立即执行的匿名函数创建,在该函数中定义了一个私有变量 privateVar 和一个私有函数 privateFunc,并通过返回一个具有一个公共方法 publicFunc 的对象来公开该模块。这样就实现了模块化开发。

  1. 缓存变量:可以使用闭包来缓存变量,避免在函数多次执行时反复计算相同的结果,提高函数的性能。
function fibonacci(n) {
  let cache = {};

  function fib(n) {
    if (n in cache) {
      return cache[n];
    } else {
      if (n < 2) {
        return n;
      } else {
        cache[n] = fib(n - 1) + fib(n - 2);
        return cache[n];
      }
    }
  }

  return fib(n);
}

console.log(fibonacci(10)); // 输出 55

在这个示例中,fibonacci 函数通过一个对象 cache 来缓存已经计算过的斐波那契数,避免在递归过程中重复计算。这样就实现了缓存变量。

闭包的缺点

  1. 内存泄漏:闭包会创建一个封闭的作用域,其中包含变量和函数,这些变量和函数在外部作用域中无法访问。这个封闭的作用域会一直存在,直到闭包本身被销毁。如果不注意,闭包可能会占用大量的内存,导致内存泄漏。

  2. 性能问题:由于闭包会在创建时捕获当前上下文的变量和函数,所以会在内存中创建新的对象,这会导致一定的性能问题。如果过度使用闭包,可能会导致应用程序变慢。

  3. 容易出错:由于闭包涉及到多个作用域和变量,所以可能会导致一些难以调试的问题。如果不小心使用闭包,可能会出现变量名冲突和作用域问题等。

因此,在使用闭包时,需要特别小心,确保其正确性和性能。在大多数情况下,可以通过其他方式实现相同的功能,而不必使用闭包。