var f = function( ) {
var x = 1
function fo() {
console.log( x++ )
}
return fo
}
var fn = f()
fn() // 1
fn() // 2
以上代码实现了一个简单的闭包,每次执行 fn 其内部变量 x 都会实现自增。
闭包用一种巧妙的方式将原本互不敢干的作用域联合了起来,我在之前的文章 JavaScript之引用函数调用与直接调用的区别 说到作用域只有在代码块执行的时候才会产生,还有一点没有提到:作用域所占的内存是如何回收的,以及何时释放。
闭包一直饱受诟病,下面具体为大家分析下我的观点:
事实上,函数体包裹的每块代码在每次执行都会新产生一个作用域,然后在函数体执行完后被释放,我们平时在执行这一个过程的时候常常忽略了这样一个事实,所以在接触到闭包这些概念的时候短时间会无法接受。当然,如果你琢磨透了的话,并没有什么难理解的,比如函数体内定义了一个变量 x = 1,每次你调用这个函数,在其内部所引用的 x 值都是1,这意味着这个变量在上次函数体被执行完后就释放了,一个独立的作用域被执行完后就会被销毁。
搞明白了上面这点,也就不难理解上面的代码,如果将函数体从一个函数内发送出去,那么就意味这此函数的作用域发射到了其上级作用域内,即两个作用域联合了起来,融合到一起了,这时候JavaScript再想销毁这个作用域就会连同 window 这个作用域一起销毁,显然它不会这么干,于是,此例中函数 fo 的作用域,也即 f 所代表的函数体(作用域)被留存了下来。
再引申开来,如果 f 这个函数体再执行一次会怎样呢?这时候一个新的函数体又被执行了,一个新的作用域产生又汇入了父级作用域,如果你想销毁这个作用域,又是不可能的。以上面这个例子来说,就有两个值为 1 的变量 x 进入了全局作用域,全局作用域不得不分配两个不同的隐式的变量指向这两个 x ,用行话来说,就是产生了全局污染。
var f = function( ) {
var x = 1
function fo() {
console.log( x++ )
}
fo.alter=function ( n ) {
x = n
}
return fo
}
var fn = f()
fn() // 1
fn() // 2
fn.alter( 10 )
fn() // 10
为了给这个函数开一个后门,使得变量 x 可以更改,给函数对象 f 增加一个 alter 的方法。
关键点在于,此方法也是在 f 的函数体内部定义的,意味着调用也会在此作用域执行,因此更改变量 x 的值也就顺利成章了。