目录1、定义2、闭包示例12.1、分析3、特性3.1、示例2闭包实现私有变量实用场景4、注意事项4.1、循环中的闭包坑5、总结1、定义闭包带着外部变量访问权限的内部函数就算外部函数执行完退出了内部函数依然能正常访问、操作它创建时所在的外部函数里的变量这些变量的访问权不会随着外部函数的结束而消失。严谨的表述当函数被创建并返回或被保存时它会携带自己的词法环境也就是创建它时所处的外部作用域这个函数 它携带的词法环境就构成了闭包。2、闭包示例11、外部函数就像一间教室里面的变量是教室里的桌椅内部函数就像一个学生闭包就是这个学生离开教室时把桌椅的 “使用权凭证” 带在了身上哪怕教室已经锁门外部函数执行完毕学生依然能使用这些桌椅访问外部变量。2、内部函数学生 外部变量的访问引用桌椅的使用权凭证 闭包。3、闭包并没有复制外部变量只是持有了外部变量的访问引用。// 外部函数functionouterFunc(){// 外部函数作用域中的变量仅在 outerFunc 内部可访问正常情况下外部无法触及letouterVar我是外部函数的变量;// 内部函数闭包的核心这个内部函数就是闭包载体functioninnerFunc(){// 内部函数访问了外部函数的变量 outerVarconsole.log(outerVar);}// 外部函数返回内部函数让内部函数可以在外部被调用returninnerFunc;}// 1. 调用外部函数得到内部函数此时 outerFunc 已经执行完毕按常理其内部变量应被销毁constmyClosureouterFunc();// 2. 调用内部函数依然能访问到 outerFunc 中的 outerVar这就是闭包的效果myClosure();// 输出我是外部函数的变量2.1、分析outerFunc 执行完毕后按照 JS 的垃圾回收机制它的内部变量 outerVar 应该被回收销毁。但因为 innerFunc 访问了 outerVar且 innerFunc 被返回并赋值给了 myClosure保留了引用所以 outerVar 不会被销毁会被 innerFunc 携带保存。当调用 myClosure() 时innerFunc 依然能从自己携带的「词法环境」中找到 outerVar这就是闭包在起作用。3、特性访问外部变量内部函数可以访问外部函数的变量甚至是外部函数的外部函数的变量词法作用域链。变量持久化外部函数执行完毕后其内部变量不会被垃圾回收而是被闭包持有直到闭包本身被销毁比如 myClosure null。隔离作用域闭包可以创建私有变量避免变量污染全局作用域这是闭包最常用的实际场景。3.1、示例2闭包实现私有变量实用场景// 实现一个简单的计数器变量 count 仅能通过 increment 和 getCount 操作无法直接修改functioncreateCounter(){// 私有变量仅在 createCounter 内部可访问全局无法直接触及letcount0;// 返回一个对象包含两个方法这两个方法都是闭包持有 count 的引用return{// 自增方法increment:function(){count;},// 获取当前值方法getCount:function(){returncount;}};}// 创建计数器实例1constcounter1createCounter();counter1.increment();counter1.increment();console.log(counter1.getCount());// 输出2// 创建计数器实例2与 counter1 互不干扰各自持有独立的 count 变量constcounter2createCounter();console.log(counter2.getCount());// 输出0示例中count 就是私有变量全局无法直接修改只能通过 increment() 和 getCount() 操作避免了变量污染这也是闭包在实际开发中的核心用途之一。4、注意事项内存泄漏风险如果闭包被长期保存比如赋值给全局变量且持有大量无用的外部变量会导致这些变量无法被垃圾回收从而造成内存泄漏。解决方案不需要使用闭包时手动解除引用比如 myClosure null。变量共享问题在循环中创建闭包时容易出现变量共享导致结果不符合预期需要注意。4.1、循环中的闭包坑// 错误示例循环中创建闭包期望输出 0、1、2实际输出 3、3、3for(vari0;i3;i){setTimeout(function(){console.log(i);// 输出3、3、3},1000);}原因分析var 声明的变量没有块级作用域i 是全局变量循环结束后 i 的值为 3。定时器中的函数是闭包它访问的是全局的 i当定时器执行时循环已经结束i 已经是 3所以输出 3 次 3。解决方案使用 let 声明变量let 有块级作用域每次循环都会创建一个独立的 i。for(leti0;i3;i){setTimeout(function(){console.log(i);// 输出0、1、2},1000);}使用立即执行函数IIFE创建独立作用域for(vari0;i3;i){(function(j){setTimeout(function(){console.log(j);// 输出0、1、2},1000);})(i);}5、总结闭包的成立必须满足两个核心条件缺一不可存在嵌套函数内部函数嵌套在外部函数中。内部函数被「带出」外部函数的作用域比如被返回、被赋值给外部变量、被传递给其他函数作为参数且内部函数访问了外部函数的变量。示例不满足条件的 “伪闭包”不算真正的闭包functionouterFunc(){letouterVar外部变量;// 内部函数仅在外部函数内部调用没有被带出外部作用域functioninnerFunc(){console.log(outerVar);}// 直接在外部函数内部执行内部函数不是闭包innerFunc();}outerFunc();// 输出外部变量这只是普通的嵌套函数不是闭包示例中虽然有内部函数访问外部变量但内部函数没有被带出外部函数的作用域外部函数执行完毕后outerVar会被正常回收不构成闭包。