返回

深入剖析 JavaScript 闭包的奥妙,揭秘经典面试题背后隐藏的智慧

前端

前言

闭包是 JavaScript 中的一项重要特性,它可以让我们创建出具有内部状态的函数,这些内部状态可以被函数外部的其他代码访问和修改。闭包在 JavaScript 中有很多应用场景,比如创建私有变量、模拟块级作用域、创建事件处理程序等。

闭包的基本概念

闭包是指能够引用另一个函数作用域中变量的函数,即使已经超出了被引用的函数的作用域,在 JavaScript 中,闭包的存在是由词法作用域决定的,词法作用域规定函数可以访问其包含作用域的变量。

例如,以下代码创建一个闭包:

function createCounter() {
  let counter = 0;

  return function() {
    return counter++;
  };
}

在这个示例中,函数 createCounter() 创建了一个闭包,该闭包引用了变量 counter,即使在 createCounter() 函数执行完之后,变量 counter 仍然可以在闭包中被访问。

闭包的应用

闭包在 JavaScript 中有很多应用场景,其中一些常见的应用场景包括:

  • 创建私有变量:闭包可以用来创建私有变量,这些变量只能被闭包函数本身访问,而不能被其他代码访问。

  • 模拟块级作用域:闭包可以用来模拟块级作用域,这在 JavaScript 中非常有用,因为 JavaScript 本身没有块级作用域。

  • 创建事件处理程序:闭包可以用来创建事件处理程序,当事件发生时,事件处理程序可以访问闭包中定义的变量。

经典面试题

一道经典的面试题是:

function createFunctions() {
  const arr = [];
  for (let i = 0; i < 3; i++) {
    arr.push(function() {
      console.log(i);
    });
  }

  return arr;
}

const functions = createFunctions();
functions[0](); // 输出 3
functions[1](); // 输出 3
functions[2](); // 输出 3

这道面试题之所以经典,是因为它考察了对闭包的理解,以及对 JavaScript 作用域的理解。

问题的根源

问题的根源在于,闭包函数引用了变量 i,而变量 icreateFunctions() 函数执行完之后就已经被销毁了,但是闭包函数仍然可以在 createFunctions() 函数执行完之后被调用。

解决方法

解决这个问题的办法是使用块级作用域,JavaScript 中的块级作用域是通过 letconst 实现的,在块级作用域中,变量只在块的内部有效,一旦块执行完,变量就会被销毁。

我们可以将 createFunctions() 函数中的 for 循环改为使用块级作用域,如下所示:

function createFunctions() {
  const arr = [];
  for (let i = 0; i < 3; i++) {
    (function() {
      const j = i;
      arr.push(function() {
        console.log(j);
      });
    })();
  }

  return arr;
}

const functions = createFunctions();
functions[0](); // 输出 0
functions[1](); // 输出 1
functions[2](); // 输出 2

在这个示例中,我们使用 let 关键字声明了变量 ij,这意味着这两个变量只在块级作用域内有效,一旦块执行完,这两个变量就会被销毁。因此,闭包函数只能引用变量 j,而变量 j 在闭包函数被调用时仍然有效,所以闭包函数可以正确地输出变量 j 的值。

总结

这道经典的面试题考察了对闭包的理解,以及对 JavaScript 作用域的理解。通过使用块级作用域,我们可以解决这个问题,并正确地输出闭包函数中的变量值。