闭包
一、 什么是闭包?
1. 通俗定义(一句话回答)
闭包就是一个函数以及它所引用的周边环境(词法环境)的组合。 简单来说,就是内部函数可以访问外部函数的变量,即使外部函数已经执行完毕。
2. 官方/学术定义 (MDN)
A closure is the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment).
二、 核心原理
闭包的形成基于 JavaScript 的 词法作用域 (Lexical Scoping) 规则:
- 函数的作用域在定义时决定,而不是调用时。
- 内部函数持有对外部函数作用域的引用(Scope Chain)。
- 当外部函数执行完毕后,因为内部函数还持有引用,所以外部函数的变量对象无法被垃圾回收机制(GC)回收,从而形成了闭包。
function createCounter() {
let count = 0; // 私有变量
return function() {
count++;
console.log(count);
}
}
const counter = createCounter();
counter(); // 1
counter(); // 2
// count 变量依然存在内存中,但外部无法直接访问
三、 闭包的常见应用场景
面试时列举 2-3 个实际场景非常加分:
1. 数据私有化 / 模拟私有变量 (最常用)
在 ES6 类出现之前,闭包是实现模块化和私有变量的主要方式。
function User(name) {
let _name = name; // 私有变量
return {
getName: function() {
return _name;
},
setName: function(newName) {
_name = newName;
}
}
}
2. 柯里化 (Currying)
将多参数函数转换成一系列单参数函数。
function makeAdder(x) {
return function(y) {
return x + y;
};
}
const add5 = makeAdder(5);
console.log(add5(2)); // 7
3. 防抖与节流 (Debounce & Throttle)
工具函数中大量使用闭包来保存定时器状态 timer。
function debounce(fn, delay) {
let timer = null; // 闭包变量,保存定时器状态
return function(...args) {
if (timer) clearTimeout(timer);
timer = setTimeout(() => fn.apply(this, args), delay);
}
}
四、 经典面试题:循环中的闭包
这是考察闭包最经典的题目,考察 var 和 let 的区别。
题目
for (var i = 1; i <= 3; i++) {
setTimeout(function() {
console.log(i);
}, 1000);
}
// 输出结果是什么?
答案
- 结果:输出三次
4。 - 原因:
var声明的变量是函数作用域(这里变成了全局),循环结束后i变成了 4。setTimeout是异步执行的,当它执行时,去读取i的值,此时i已经是 4 了。
怎么修改为输出 1, 2, 3?
方法 1:使用 let (推荐) let 有块级作用域,每次循环都会创建一个新的绑定。
for (let i = 1; i <= 3; i++) {
setTimeout(function() { console.log(i); }, 1000);
}
方法 2:使用立即执行函数 (IIFE) 构造闭包 通过传参,将当前的 i 保存为局部变量 j。
for (var i = 1; i <= 3; i++) {
(function(j) {
setTimeout(function() { console.log(j); }, 1000);
})(i);
}
五、 闭包的缺点与内存泄漏
-
缺点:闭包会让变量始终保存在内存中,不会被垃圾回收。如果不当使用,会导致内存消耗过大。
-
内存泄漏误区:闭包本身不是内存泄漏(它是 JS 的特性)。但如果闭包持有的大量对象不再需要使用,却一直不释放,就会导致内存泄漏。
-
解决方法:将不再使用的闭包引用置为
null。text123let counter = createCounter(); counter(); counter = null; // 解除引用,允许 GC 回收
六、 面试高分回答策略 (Cheat Sheet)
如果面试官问:“讲讲闭包”,你可以这样回答:
-
下定义:
“闭包是指有权访问另一个函数作用域中变量的函数。简单来说,就是内部函数引用了外部函数的变量,导致这些变量无法被垃圾回收。”
-
讲原理:
“它的核心原理是基于 JavaScript 的词法作用域链。内部函数在定义时就记住了它所处的环境。”
-
举例子:
“在实际开发中,闭包主要有两个用途: 一是保护变量,比如实现数据私有化、模块化; 二是保存变量,比如在防抖节流函数中保存 timer,或者在柯里化函数中保存预设参数。”
-
提注意:
“不过使用闭包要注意内存管理。因为它会阻止垃圾回收,滥用可能导致内存占用过高。在不再使用时,我会手动将引用置为 null。”