闭包的概念与应用

闭包的概念与应用

什么是闭包(Closure)

闭包 = 函数 + 它创建时所处的词法作用域。

也就是说:一个函数即使离开了它原本的作用域,仍然可以访问创建时作用域中的变量。

functionouter(){letcount=0functioninner(){count++console.log(count)}returninner}constfn=outer()fn()// 1fn()// 2fn()// 3

outer()已经执行结束了,但inner仍然能访问count,这就是闭包。


为什么count没有被销毁?

正常情况下,函数执行完成后,其局部变量可能会被回收。

但这里:

constfn=outer()

fn引用了inner函数,而inner又引用了外层变量count

引用关系大致是:

fn ↓ inner 函数 ↓ outer 的词法作用域 ↓ count

因为count仍然被使用,所以不会被垃圾回收。


闭包的核心条件

一般可以这样判断:

  1. 存在函数嵌套。
  2. 内部函数使用了外部函数的变量。
  3. 内部函数在外部函数执行结束后仍然被使用。
functionouter(){constname='小明'returnfunctioninner(){console.log(name)}}

常见用途

1. 保存状态

functioncreateCounter(){letcount=0returnfunction(){return++count}}constcounter1=createCounter()constcounter2=createCounter()console.log(counter1())// 1console.log(counter1())// 2console.log(counter2())// 1

每调用一次createCounter(),都会创建一个新的独立作用域。


2. 实现数据私有化

functioncreateWallet(){letmoney=100return{getMoney(){returnmoney},deposit(amount){money+=amount},withdraw(amount){if(amount<=money){money-=amount}}}}constwallet=createWallet()wallet.deposit(50)console.log(wallet.getMoney())// 150console.log(wallet.money)// undefined

外部不能直接访问money,只能通过暴露的方法操作。


3. 函数柯里化和参数复用

functionmultiply(a){returnfunction(b){returna*b}}constdouble=multiply(2)consttriple=multiply(3)console.log(double(5))// 10console.log(triple(5))// 15

double通过闭包记住了a = 2


4. 防抖

functiondebounce(fn,delay){lettimer=nullreturnfunction(...args){clearTimeout(timer)timer=setTimeout(()=>{fn.apply(this,args)},delay)}}

返回的函数通过闭包持续访问timer,所以能够取消上一次定时器。


经典面试题:循环中的闭包

使用var

for(vari=0;i<3;i++){setTimeout(function(){console.log(i)},0)}

输出:

3 3 3

原因是:

  • var没有块级作用域;
  • 三个回调函数共享同一个i
  • 回调执行时,循环已经结束,i变成了3

使用let

for(leti=0;i<3;i++){setTimeout(function(){console.log(i)},0)}

输出:

0 1 2

因为let在每次循环中都会创建一个新的块级绑定,每个回调闭包保存的是不同的i

使用立即执行函数解决

for(vari=0;i<3;i++){;(function(currentI){setTimeout(function(){console.log(currentI)},0)})(i)}

闭包不一定要return

很多人误以为只有返回内部函数才算闭包,其实不是。

functionouter(){constmessage='hello'setTimeout(function(){console.log(message)},1000)}outer()

传给setTimeout的回调函数使用了message,它同样形成了闭包。


闭包可能导致内存问题吗?

闭包本身不是内存泄漏,但如果不再需要的变量仍然被闭包引用,就可能无法及时回收。

functioncreateHandler(){consthugeData=newArray(1000000).fill('*')returnfunction(){console.log(hugeData.length)}}lethandler=createHandler()

只要handler仍然存在,hugeData就可能继续保留。

不需要时可以解除引用:

handler=null

现代 JavaScript 引擎也会进行作用域分析,通常只保留真正被内部函数使用的变量,而不是无条件保留所有局部变量。


闭包和作用域链的关系

constglobalValue='global'functionouter(){constouterValue='outer'returnfunctioninner(){constinnerValue='inner'console.log(innerValue)console.log(outerValue)console.log(globalValue)}}

inner查找变量时会按照作用域链向外查找:

inner 作用域 ↓ outer 作用域 ↓ 全局作用域

闭包的本质并不是“保存一个变量副本”,而是函数保留了对其词法环境的访问能力。

面试回答模板

闭包是指函数与其创建时的词法作用域的组合。当内部函数引用外部作用域中的变量,并且该函数在外部函数执行结束后仍然存在时,外部变量不会被垃圾回收。闭包常用于保存状态、数据私有化、防抖节流和函数柯里化,但如果长期持有不再需要的数据,也可能增加内存占用。