1. 闭包的定义和作用
定义:闭包是一个函数和其周围的状态(词法环境)的组合。闭包让你可以从内部函数访问外部函数作用域中的变量。
作用:
- 封装私有变量:闭包可以用来隐藏内部变量,防止外部直接访问。
- 保持状态:闭包可以捕获外部函数的变量,即使外部函数已经执行完毕。
2. 常见闭包面试题
题目1:理解闭包的输出
function outerFunc() {
let a = 10;
function innerFunc() {
console.log(a);
}
return innerFunc;
}
let innerFunc = outerFunc();
innerFunc(); // 输出什么?
答案:输出 10。innerFunc 是一个闭包,它捕获了 outerFunc 的变量 a,即使 outerFunc 已经执行完毕。
题目2:闭包与 IIFE
(function(a) {
return (function(b) {
console.log(a);
})(1);
})(0);
答案:输出 0。外层函数的参数 a 被内层函数捕获,内层函数是一个闭包。
题目3:闭包与循环
for (var i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i);
}, 0);
}
答案:输出 5, 5, 5, 5, 5。由于 var 的作用域是全局,循环结束后 i 的值为 5,所有闭包都引用了同一个变量。
改进:使用 IIFE 或 let 解决:
for (var i = 0; i < 5; i++) {
(function(j) {
setTimeout(function() {
console.log(j);
}, 0);
})(i);
}
或者:
for (let i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i);
}, 0);
}
题目4:闭包与模块模式
function createCounter() {
let count = 0;
return function() {
count++;
return count;
};
}
let counter1 = createCounter();
let counter2 = createCounter();
console.log(counter1()); // 输出什么?
console.log(counter1()); // 输出什么?
console.log(counter2()); // 输出什么?
答案:输出 1, 2, 1。counter1 和 counter2 是独立的闭包,分别捕获了独立的 count。
题目5:闭包与私有变量
function Person() {
let name = "Alice";
this.getName = function() {
return name;
};
}
const person = new Person();
console.log(person.getName()); // 输出什么?
console.log(person.name); // 输出什么?
答案:输出 Alice 和 undefined。name 是私有变量,只能通过 getName 方法访问。
3. 面试中如何回答闭包问题
面试官可能会问:“请解释什么是闭包?”或“闭包有什么作用?”以下是一个完整的回答模板:
- 定义:闭包是一个函数和其周围的状态(词法环境)的组合。它允许内部函数访问外部函数的变量。
- 作用:
-
- 封装私有变量:通过闭包可以隐藏内部变量,防止外部直接访问。
- 保持状态:即使外部函数执行完毕,闭包仍然可以访问其捕获的变量。
- 示例:可以结合上述题目中的代码进行解释。
4. 面试中常见的陷阱
- 变量提升:闭包可能会捕获变量的最终值,而不是期望的值(如循环中的变量)。
- 内存泄漏:闭包会延长变量的生命周期,可能导致内存泄漏。
- 异步问题:闭包在异步回调中可能会导致意外的行为。
5. 如何避免闭包中的问题
- 使用
let替代var:let的块级作用域可以避免变量提升问题。 - 手动清理引用:如果不再需要闭包中的变量,可以手动将其设置为
null。 - 合理使用 IIFE:通过立即执行函数表达式(IIFE)创建独立的作用域。
通过这些面试题和技巧,你可以更好地理解和应用闭包,同时避免常见的陷阱。