好的,继续 第 25 题:闭包(Closure)与作用域链(详细 + 面试模板 + 速记卡)
第 25 题:闭包(Closure)与作用域链
闭包是前端面试必考题,几乎每家公司都会问。
我会用非常详细但清晰的方式讲解。
一、闭包是什么?
闭包 = 函数 + 其定义时的词法环境(外部变量)
简单说:
闭包让函数可以"记住"它定义时的外部变量,即使外部函数已经执行完毕。
比如:
scss
function outer() {
let count = 0;
return function inner() {
count++;
console.log(count);
};
}
const fn = outer();
fn(); // 1
fn(); // 2
即使 outer() 执行完,count 仍然能被访问,这就是闭包。
二、闭包形成的原因:词法作用域
JS 采用 词法作用域(静态作用域) ,作用域在函数定义时已经确定。
所以 inner 函数能访问 outer 的变量。
编译阶段就决定了作用域链,运行时不会改变。
三、闭包的用途(面试重点)
1. 创建私有变量
javascript
function createCounter() {
let num = 0;
return {
inc() { num++; },
get() { return num; }
}
}
2. 防抖 / 节流、缓存函数里面常用
vbnet
function cache(fn) {
const map = {};
return function(key) {
if (map[key]) return map[key];
return map[key] = fn(key);
};
}
3. 柯里化 Currying
css
const add = a => b => a + b;
4. 模拟块级作用域(ES5 时代)
javascript
for (var i = 0; i < 3; i++) {
setTimeout((function(i){
return function() { console.log(i) }
})(i), 1000);
}
四、闭包的缺点(面试必问)
❗闭包会导致变量常驻内存,可能造成内存泄漏
例如:
javascript
function test() {
let data = new Array(100000).fill(0);
return function() {
console.log(data.length);
}
}
const f = test();
data 一直无法释放,因为闭包引用它。
五、作用域链(Scope Chain)
作用域链 = 当前作用域 → 外部作用域 → 全局作用域
查变量是从内到外一层层查。
示例:
ini
var a = 1;
function f1() {
var b = 2;
function f2() {
var c = 3;
console.log(a, b, c);
}
f2();
}
f1();
作用域链查找顺序:
csharp
f2 → f1 → global
六、面试官常问三连
① 闭包是什么?
闭包是在函数创建时,把函数和它的词法作用域绑定在一起,使函数在外部作用域结束之后仍然能访问该作用域的变量。
② 闭包有什么用?
私有变量、缓存、柯里化、防抖节流内部状态、回调函数等。
③ 闭包有什么风险?
变量不会释放,容易造成内存泄漏,如不需要应置为 null 或使用块级作用域优化。
七、经典闭包题 --- 面试必考
❗例题:
css
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 0);
}
输出?
3 3 3
原因:
- var 没有块级作用域
- 三个回调共享同一个 i
- 当执行回调时,i 已变为 3
改成 let:
css
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 0);
}
输出:
0 1 2
八、速记卡片
闭包 = 函数 + 外部变量
作用:记住状态(私有变量、缓存)
关键:词法作用域决定作用域链
风险:内存泄漏(变量无法释放)
如果你准备好了,我可以继续 第 26 题:前端性能优化(重排/重绘、懒加载、打包优化) 。
说一句:"下一题"即可。