什么是闭包Closure闭包 函数 它创建时所处的词法作用域。也就是说一个函数即使离开了它原本的作用域仍然可以访问创建时作用域中的变量。functionouter(){letcount0functioninner(){countconsole.log(count)}returninner}constfnouter()fn()// 1fn()// 2fn()// 3outer()已经执行结束了但inner仍然能访问count这就是闭包。为什么count没有被销毁正常情况下函数执行完成后其局部变量可能会被回收。但这里constfnouter()fn引用了inner函数而inner又引用了外层变量count。引用关系大致是fn ↓ inner 函数 ↓ outer 的词法作用域 ↓ count因为count仍然被使用所以不会被垃圾回收。闭包的核心条件一般可以这样判断存在函数嵌套。内部函数使用了外部函数的变量。内部函数在外部函数执行结束后仍然被使用。functionouter(){constname小明returnfunctioninner(){console.log(name)}}常见用途1. 保存状态functioncreateCounter(){letcount0returnfunction(){returncount}}constcounter1createCounter()constcounter2createCounter()console.log(counter1())// 1console.log(counter1())// 2console.log(counter2())// 1每调用一次createCounter()都会创建一个新的独立作用域。2. 实现数据私有化functioncreateWallet(){letmoney100return{getMoney(){returnmoney},deposit(amount){moneyamount},withdraw(amount){if(amountmoney){money-amount}}}}constwalletcreateWallet()wallet.deposit(50)console.log(wallet.getMoney())// 150console.log(wallet.money)// undefined外部不能直接访问money只能通过暴露的方法操作。3. 函数柯里化和参数复用functionmultiply(a){returnfunction(b){returna*b}}constdoublemultiply(2)consttriplemultiply(3)console.log(double(5))// 10console.log(triple(5))// 15double通过闭包记住了a 2。4. 防抖functiondebounce(fn,delay){lettimernullreturnfunction(...args){clearTimeout(timer)timersetTimeout((){fn.apply(this,args)},delay)}}返回的函数通过闭包持续访问timer所以能够取消上一次定时器。经典面试题循环中的闭包使用varfor(vari0;i3;i){setTimeout(function(){console.log(i)},0)}输出3 3 3原因是var没有块级作用域三个回调函数共享同一个i回调执行时循环已经结束i变成了3。使用letfor(leti0;i3;i){setTimeout(function(){console.log(i)},0)}输出0 1 2因为let在每次循环中都会创建一个新的块级绑定每个回调闭包保存的是不同的i。使用立即执行函数解决for(vari0;i3;i){;(function(currentI){setTimeout(function(){console.log(currentI)},0)})(i)}闭包不一定要return很多人误以为只有返回内部函数才算闭包其实不是。functionouter(){constmessagehellosetTimeout(function(){console.log(message)},1000)}outer()传给setTimeout的回调函数使用了message它同样形成了闭包。闭包可能导致内存问题吗闭包本身不是内存泄漏但如果不再需要的变量仍然被闭包引用就可能无法及时回收。functioncreateHandler(){consthugeDatanewArray(1000000).fill(*)returnfunction(){console.log(hugeData.length)}}lethandlercreateHandler()只要handler仍然存在hugeData就可能继续保留。不需要时可以解除引用handlernull现代 JavaScript 引擎也会进行作用域分析通常只保留真正被内部函数使用的变量而不是无条件保留所有局部变量。闭包和作用域链的关系constglobalValueglobalfunctionouter(){constouterValueouterreturnfunctioninner(){constinnerValueinnerconsole.log(innerValue)console.log(outerValue)console.log(globalValue)}}inner查找变量时会按照作用域链向外查找inner 作用域 ↓ outer 作用域 ↓ 全局作用域闭包的本质并不是“保存一个变量副本”而是函数保留了对其词法环境的访问能力。面试回答模板闭包是指函数与其创建时的词法作用域的组合。当内部函数引用外部作用域中的变量并且该函数在外部函数执行结束后仍然存在时外部变量不会被垃圾回收。闭包常用于保存状态、数据私有化、防抖节流和函数柯里化但如果长期持有不再需要的数据也可能增加内存占用。