Appearance
闭包
定义
官方定义 闭包 = 函数 + 函数能访问的词法作用域
一句话总结 闭包 = 内部函数 + 引用外部函数变量 + 外部函数执行完毕不销毁
示例
js
function outer(){
let count = 0;
function inner(){
count++;
console.log(count);
}
return inner;
}
const fn = outer();
fn(); // 1
fn(); // 2
fn(); // 3- inner 引用了 outer 的 count
- outer 执行完不会销毁作用域
- fn 每次调用都操作同一个 count
原理
- 词法作用域 函数在哪里定义,就能访问哪里的变量,和在哪执行无关
- 作用域不销毁 正常函数执行完 → 作用域销毁 闭包函数 → 作用域被保留(因为被引用)
- 垃圾回收机制 没有引用 → 被回收 闭包持有引用 → 不回收
用途
1.私有化变量(模拟私有属性)
js
function createPerson(){
let name="睡觉";
return {
getName: ()=>name,
setName: (newName)=>{
name = newName;
}
}
}
const p = createPerson();
console.log(p.getName());//睡觉
p.setName("嘻嘻");
console.log(p.setName());
console.log(p.name);//undefined2.缓存/持久化数据
闭包可以让变量常驻内存,实现缓存。
3.函数柯里化
js
function add(a){
return function(b){
return a+b;
}
}
const add = add(10);
console.log(add(1));//114.解决循环中异步取值问题
js
//输出都是5
for(var i=0;i<5;i++){
setTimeout(()=>console.log(i),1000);
}
//闭包
for(var i=0;i<5;i++){
((j)=>{
setTimeout(()=>console.log(j),1000);
})(i);
}优缺点
优点
- 私有化变量,避免全局污染
- 可以缓存数据
- 函数式编程核心(柯里化、工厂函数)
- 模块化基础
缺点
- 内存泄漏风险(变量常驻)
- 过度使用会影响性能
- 调试困难(作用域链复杂)
面试常问
- 什么是闭包
- 闭包是内部函数引用了外部函数作用域的变量;
- 即使外部函数执行完毕,内部函数依然可以访问外层作用域变量;
- 本质:函数 + 词法作用域的组合,作用域不会被 GC 回收。
- 闭包产生的条件?
- 函数嵌套
- 内部函数引用外层函数的变量/参数
- 内部函数在外层作用域之外被调用
- 闭包的作用 / 应用场景?
- 实现变量私有化
- 保存局部变量,做数据缓存、状态持久化
- 函数柯里化,高阶函数封装
- 解决循环异步定时器变量捕获
- 模块化思想、防抖 / 节流 / 计数器底层都用到闭包。
- 闭包缺点
- 外层作用域变量常驻内存,无法被垃圾回收
- 容易造成内存泄漏
- 滥用会导致代码可读性变差,性能下降
- 闭包为什么会造成内存泄漏?
- 正常函数执行完,局部作用域会销毁、变量被 GC 回收;
- 但闭包会持续持有外层变量的引用,浏览器无法回收这块内存,长期堆积就会引发内存泄漏。
- 怎么解决闭包内存泄漏?
- 手动切断引用:把接收闭包函数的变量赋值为 null
- 尽量减少不必要的全局闭包
- 及时销毁定时器、事件监听等闭包场景
js
let fn = outer();
fn = null; // 解除引用,等待GC回收- 循环 + setTimeout 经典闭包题
js
for(var i=0;i<5;i++){
setTimeout(()=>{
console.log(i)
},1000)
}输出:5 5 5 5 5
原因:
var 没有块级作用域,全局只有一个 i;
定时器回调是闭包,延迟执行时循环早已结束,i 最终变成 5。
三种解决方式:
方案 1:用 let 块级作用域(最简单)
方案 2:立即执行函数 (IIFE) + 闭包保存每一轮变量
方案 3:定时器第三个参数传参
- let/var 对闭包的影响?
- var 函数级作用域,循环共享同一个变量,闭包会捕获同一个值;
- let 块级作用域,每次循环都会生成独立绑定,每个闭包捕获独立变量,不会错乱。
- 作用域、作用域链、闭包的关系
- 作用域:变量的可访问范围;
- 作用域链:变量查找的逐级规则;
- 闭包:利用作用域链,跨作用域保留变量引用。