JavaScript中的闭包是指能够访问其他函数作用域内变量的函数,即一个函数内部的函数。闭包是一种保护私有变量的机制,在函数执行时形成私有的作用域,保护里面的私有变量不受外界干扰,即形成一个不销毁的栈环境。但由于闭包会使得函数中的变量都被保存在内存中,所以滥用闭包会造成性能问题或是导致内存泄漏。
JavaScript闭包简介
首先来看一个简单的例子:
1 | var a = 1; |
在这个例子中,fn1使用的是全局变量a,fn2使用的是局部变量a。多次执行函数后,f1返回的是多次累加的结果,而f2由于使用的是局部变量执行结果始终与单次执行相同。如果希望多次对一个变量进行操作,同时又不希望将其暴露在全局之中,此时上面的两个函数均无法满足需求,可使用嵌套函数的方法。在JavaScript中,所有函数都能访问它们上一层的作用域。嵌套函数可以访问上一层的函数变量。例如:
1 | var add = (function f1() { |
上面的这个函数就是一个JavaScript闭包,它使得函数拥有私有变量变成可能。
作用域与作用域链
JavaScript中存在如下三种作用域:
1 | // 全局作用域 |
当同时存在多个作用域对象时,所有函数的作用域对象会被环境栈所管理。环境栈中的作用域对象是按顺序访问的,最先能够访问的是当前函数的作用域,如果访问的变量在当前作用域没有,会访问上一层作用域,直到找到全局作用域。如果访问到全局作用域也没有这个对象,会抛出ReferenceError的异常。
简单来说就是前面提到过的所有函数都能访问它们上一层的作用域
,子对象会一级级向上寻找所有父对象的变量,函数内部可以读取全局变量,这就是所谓的作用域链。
JavaScript闭包原理
还是以上面提到的闭包为例,上面的例子中多次调用add函数后变量counter的值能够持续增加,原因就在于每次执行完add之后counter并没有被回收。
简单来说,其过程就是:
- 全局变量add引用了函数f1自我调用后的返回值,即函数f2。
- f1在自我调用时只执行一次,并将counter初始化为0。
- add变量可以作为一个函数使用,它可以访问函数上一层作用域的变量,即f1中的counter。
- 在执行完一次add()之后,由于f2被全局变量add引用了,所以不会被回收销毁。
- 而f1又引用了f2中的变量,所以f2作用域也不会被销毁,下一次执行时counter值不会重置。
这样一来,就可以对变量counter进行多次操作,同时counter受函数f1的作用域保护,只能通过add方法修改,不会暴露在全局中,上一节中提到的需求通过闭包实现了。
闭包产生的三个必要条件为:
- 在函数A内部直接或者间接返回一个函数B
- B函数内部使用着A函数的私有变量(私有数据)
- A函数外部有一个变量接受着函数B
JavaScript垃圾回收机制
JavaScript具有自动垃圾回收机制。后台的垃圾回收器会监视所有对象,按照固定的时间间隔周期性地执行垃圾回收,删除那些不可达的对象。
对象的可达性遵循下面的原则:
- 本地函数的局部变量和参数、当前嵌套调用链上的其他函数的变量和参数、全局变量等是可达的。
- 如果一个对象被引用,则它是可达的
- 如果若干个对象互相引用成环,且没被其他对象引用,则这些对象都是不可达的