深入剖析 JavaScript 闭包的奥妙,揭秘经典面试题背后隐藏的智慧
2023-10-03 14:05:31
前言
闭包是 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
,而变量 i
在 createFunctions()
函数执行完之后就已经被销毁了,但是闭包函数仍然可以在 createFunctions()
函数执行完之后被调用。
解决方法
解决这个问题的办法是使用块级作用域,JavaScript 中的块级作用域是通过 let
和 const
实现的,在块级作用域中,变量只在块的内部有效,一旦块执行完,变量就会被销毁。
我们可以将 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
关键字声明了变量 i
和 j
,这意味着这两个变量只在块级作用域内有效,一旦块执行完,这两个变量就会被销毁。因此,闭包函数只能引用变量 j
,而变量 j
在闭包函数被调用时仍然有效,所以闭包函数可以正确地输出变量 j
的值。
总结
这道经典的面试题考察了对闭包的理解,以及对 JavaScript 作用域的理解。通过使用块级作用域,我们可以解决这个问题,并正确地输出闭包函数中的变量值。