一、闭包核心原理(图解版)
1. 闭包的形成条件
function outer() {
let count = 0; // 自由变量
return function inner() {
count++;
return count;
};
}
const fn = outer();
fn(); // 1
- 关键点:当内部函数(
inner)引用了外部函数(outer)的变量(count)时,即使外部函数执行完毕,其变量仍被保留,形成闭包
2. 内存模型
栈内存:outer执行上下文销毁
堆内存:count变量被inner函数的作用域链引用 → 无法被GC回收
- Chrome DevTools验证:通过Memory面板抓取闭包对象
3. 作用域链本质
inner.[[Scopes]] = [
Closure (outer), // 闭包对象
Global
]
二、面试高频问题+答案模板
问题1:什么是闭包?
答:
闭包是指有权访问另一个函数作用域中变量的函数,其核心是作用域链的保留。当函数A返回内部函数B,且B使用了A的变量时,即使A执行完毕,B仍能通过作用域链访问到A的变量,这种组合就是闭包。
加分回答:
"从V8引擎实现角度看,闭包其实是外层函数执行上下文被销毁时,其内层函数仍然持有对外部变量对象的引用,导致变量对象无法被GC回收"
问题2:闭包会导致内存泄漏吗?如何避免?
答:
闭包本身不会必然导致内存泄漏,但滥用会导致预期外的内存保留。例如:
function leak() {
const hugeData = new Array(1000000);
return () => { console.log('hi') };
// 虽然没使用hugeData,但部分JS引擎可能仍保留
}
解决方案:
- 在不再需要时手动解除引用:
fn = null - 使用Chrome DevTools的Memory面板定位问题闭包
- 避免在闭包中保留不需要的大对象
加分回答:
"现代浏览器引擎(如V8)通过逃逸分析优化,如果检测到内部函数未实际使用外部变量,会主动回收外层变量"
问题3:手写一个闭包的实际应用场景
答案模板:
// 防抖函数(高频面试题!)
function debounce(fn, delay) {
let timer = null;
return function(...args) {
clearTimeout(timer); // 关键:闭包保留timer
timer = setTimeout(() => {
fn.apply(this, args);
}, delay);
};
}
// 使用
const inputHandler = debounce((val) => {
console.log('搜索:', val);
}, 300);
input.addEventListener('input', e => inputHandler(e.target.value));
技术点:
- 闭包保存
timer状态 - 高频触发时清除旧定时器
- 保持
this指向正确性
问题4:如何用闭包实现模块化?
答案模板:
const counterModule = (() => {
let count = 0; // 私有变量
return {
increment() {
count++;
},
get() {
return count;
}
};
})();
counterModule.increment();
console.log(counterModule.get()); // 1
技术点:
- IIFE立即执行创建闭包
- 暴露公共方法,隐藏私有变量
- 对比ES6 Class的
#私有字段语法差异
问题5:React Hooks中的闭包陷阱
场景复现:
function App() {
const [count, setCount] = useState(0);
useEffect(() => {
const timer = setInterval(() => {
console.log(count); // 总是输出初始值0
}, 1000);
return () => clearInterval(timer);
}, []); // 空依赖
return <button onClick={() => setCount(c => c+1)}>+</button>;
}
原因分析:
- 闭包捕获了初次渲染时的
count值 - 依赖数组为空导致effect不会重新执行
解决方案:
- 使用函数式更新:
setCount(c => c+1) - 通过
useRef保存可变值 - 正确声明依赖项:
[count]
三、闭包面试进阶方向
- 性能优化:如何用闭包实现缓存(Memoization)
- 框架原理:Vue的响应式系统中如何用闭包跟踪依赖
- 异步场景:闭包在Promise链式调用中的应用
- 安全相关:闭包实现私有变量 vs WeakMap方案对比
四、闭包相关的手写真题
题目1:实现一个累加器
function createAccumulator(initial) {
let value = initial;
return function(num) {
value += num;
return value;
};
}
const acc = createAccumulator(5);
console.log(acc(2)); // 7
console.log(acc(3)); // 10
题目2:实现私有变量
function Person(name) {
let _age = 0;
return {
getName() { return name },
setAge(age) { _age = age },
getAge() { return _age }
};
}
const p = Person('Alice');
p.setAge(30);
console.log(p.getAge()); // 30
console.log(p.name); // undefined
掌握闭包的关键是理解词法作用域和垃圾回收机制的交互,建议用Chrome调试器实际观察闭包变量的生命周期。在面试中遇到闭包问题时,务必结合具体业务场景回答,突出工程实践经验