闭包
首先很多人一看到这个词都会想,闭包是什么,它为什么会这么定义,简单来说,闭包是指一个内部嵌入函数可以访问其外部作用域的变量。
在 JavaScript 中,变量的作用域由函数界定(即 “函数作用域”)。通常函数执行结束后,其内部作用域会被系统清理,相关变量也会被垃圾回收以释放内存。但闭包是特殊情况 —— 它是定义在一个函数(外层函数)内部的子函数,关键在于会引用外层函数的变量或参数。
正因为这种引用关系,即便外层函数已经执行完毕,它的作用域也不会被销毁(毕竟闭包还需要访问其中的变量)。此时这个内层函数(闭包)就相当于 “保留” 了对上级作用域的访问权限,外层作用域里的变量也会一直存在于内存中,不会随函数执行结束而消失。
说到底,闭包的形成,核心是和变量的作用域规则、以及变量的生存周期这两个因素紧密绑定的 —— 三者相互关联,才让闭包拥有了 “持久访问外层变量” 的特性。
1. 变量的作用域
本质就是变量能被访问的有效范围。代码执行时,会顺着执行环境生成的作用域链逐层向外查找变量,直到找到全局对象才会停止。
2. 变量的生存周期
全局变量的生存周期是永久的,除非主动对其进行销毁操作;而函数作用域内的变量,或是用 let、const 声明的局部变量,通常会在函数执行完毕后,随着作用域的销毁而被回收。但如果函数内部的子函数(闭包)引用了外层变量,这类变量的回收就会遵循引用计数等垃圾回收机制 —— 只要闭包还在引用,变量就不会被销毁。
3. 内存泄漏相关
在 IE 浏览器中,JavaScript 对象和 DOM 对象的垃圾收集机制并不相同。正因为这种差异,闭包在 IE 中容易引发内存泄漏问题,具体表现为原本该被销毁的 DOM 元素,会因为闭包的引用而一直占用内存,无法正常释放。
闭包核心总结
1. 概念
函数嵌套时,内层函数引用外层变量形成的特殊结构,可跨作用域访问变量。
2. 作用
- 实现变量私有化,避免全局污染;
- 延长变量生命周期,持久化状态;
- 封装逻辑,支持模块化开发。
3. 优点
- 灵活保留函数执行上下文;
- 无需全局变量即可共享数据;
- 简化代码模块化实现。
4. 缺点
- 占用额外内存,滥用易导致内存溢出;
- IE 浏览器中可能引发 DOM 相关内存泄漏。
闭包常见场景
1. 场景:实现计数器(需持久化计数状态,不污染全局)
javascript
运行
function createCounter() {
let count = 0; // 私有变量
return () => count++; // 闭包:引用外层count
}
const add = createCounter();
console.log(add()); // 1
console.log(add()); // 2
2. 场景:缓存计算结果(避免重复运算,提升效率)
javascript
运行
function createCache() {
const cache = {}; // 缓存容器
return (key, val) => {
if (cache[key]) return cache[key];
cache[key] = val; // 闭包保留cache状态
return val;
};
}
const setCache = createCache();
setCache("name", "张三");
console.log(setCache("name")); // 直接读取缓存:张三
3. 场景:模块化封装(隐藏私有逻辑,只暴露公共接口)
javascript
运行
const userModule = (function() {
const password = "123456"; // 私有变量
return {
checkPwd: (pwd) => pwd === password // 闭包访问password
};
})();
console.log(userModule.checkPwd("123456")); // true
console.log(userModule.password); // undefined(无法直接访问)
高频面试题
面试题 1 var 作用域 + 闭包共享变量
javascript
运行
var arr = [];
for (var i = 0; i < 3; i++) {
arr[i] = function() { console.log(i); };
}
arr[0](); // 结果:3
arr[1](); // 结果:3
解析:var 声明的 i 是全局作用域,循环结束后 i=3;数组内函数是闭包,共享同一个 i。
面试题 2 闭包实现变量私有化
javascript
运行
function Person() {
var name = "张三";
this.getName = () => name;
this.setName = (n) => name = n;
}
var p = new Person();
p.name; // 结果:undefined
p.getName(); // 结果:张三
p.setName("李四");
p.getName(); // 结果:李四
解析:name 是私有变量,外部无法直接访问;getName/setName 是闭包,可访问 name。
面试题 3 闭包的独立作用域
javascript
运行
function fn() {
var num = 10;
return () => { num++; console.log(num); };
}
var f1 = fn();
var f2 = fn();
f1(); // 结果:11
f1(); // 结果:12
f2(); // 结果:11
解析:每次调用 fn () 创建独立作用域,f1/f2 对应不同的 num。
ps: 非常感谢大家能够看到最后,祝大家可以在前端的学习下共同成长,一起变得更优秀!!!