闭包

关于闭包的理解

理解闭包,首先要清楚垃圾回收

垃圾回收

JavaScript高级程序设计中,对垃圾回收描述如下:

原理是找出那些不在使用的变量,然后释放其占用的内存。为此,垃圾收集器会在固定的时间间隔,周期性的进行垃圾回收。

那么,理解一个函数(函数作用域)中所定义的局部变量的声明周期就很重要。

函数在定义时,函数并没有被执行,函数体内的代码就没有效果。

函数调用时,函数体内的代码才开始执行,此时代码跳转到函数定义时。此时,就会新建一个作用域(注意,是函数定义时的作用域而不是函数调用时)。

在函数作用域内,我们把函数内的arguments及定义的变量(局部变量),打包成活动对象,此时垃圾回收器将会对其进行标记(进入环境),当函数执行完毕后,会被标记(离开环境)。注意,因为垃圾回收器时定时回收,此时只是标记并没有被回收。

当垃圾回收器的周期到达回收时,他会对所有的变量进行表示(我要回收你),然后对当前作用域中的变量以及被当前作用域中的变量引用的变量去除标记(我不回收你了)。然后回收。

所以,垃圾回收器的工作机制就是定期打标签,把被引用的就都去掉标签,然后回收掉有标签的。

作用域

在es5中,js只有全局作用域和函数作用域(es6中使用let关键词会把let外{}包裹的部分变为一个块级作用域)。

作用域内可以访问作用域外的变量,反之则不行。

闭包时到底发生了什么

当一个函数定义时,函数体内的代码可以暂时忽略。

函数调用时,会跳转到函数定义时所在的作用域,并把作用域内的arguments伪数组及内部定义的变量打包成一个活动对象(如果内部定义了函数,那么这个活动函数也会被打包到对象内)。

因为js的垃圾回收机制是标记清除,如果最终这个对象没有引用了,就被清除。

但是,如果函数内部定义了一个函数,并且这个函数在外部被引用了,那么垃圾回收机制就不能把外部函数的活动对象标记清除,外部函数的活动对象就保存下来了。

那么在函数外部调用这个内部函数,因为也会在内部上执行创建活动对象,标记活动对象,清除活动对象这个过程。

因为内部函数的活动对象是内部函数作用域的变量,正好外部函数的活动对象还清除不了,所以就可以访问外部函数的活动对象。把这个对象保存的东西拿过来用。

这就应该是闭包了。

总结一下,只要内部函数指向了外部函数的外部,外部函数执行完之后的活动对象就不会被回收。

所以,缺点显而易见,影响自动垃圾回收,占用内存。

但是也可以使用这个特性来定义私有变量,因为只能通过内部函数访问到外部函数的活动对象。

最后,解决占用内存的办法也很简单,就是不在让外部函数的外面引用内部函数,这样垃圾回收机制就会进行标记,清空内存。

所以,看一个变量在不在可以分析它的生命周期及所处的作用域。