一、从使用角度谈闭包
满足以下列条件的就是闭包:
1.定义一个函数(outer),该函数存在声明的局部变量(b)
2.该函数的返回值也是一个函数(inner)
3.返回的函数(inner)调用了该函数声明的局部变量(b)
4.该函数被调用(outer)
function outer() {
var b = 1;
function inner() {
console.log(b);
}
return inner;
}
// 该函数被调用
outer()
二、从作用域角度谈闭包
闭包实际就是一种能使局部变量不被销毁的特殊写法。
在ES5时代,变量只有2种:全局变量和函数产生的局部变量。
全局变量可以被任意引用的外部文件、环境、代码覆盖,并且不会有任何提示。如果我们想要一个不会被随意修改的变量、只能由特定方式触发变化的安全变量,只能考虑函数的局部变量。
函数内的局部变量不能被外部任意访问,只能在该函数内被引用,这似乎是个极好的、使变量保持纯净的方法。
但我们知道,函数在被调用时才会加载至内存,调用一旦结束就会被销毁,所以一般函数产生的局部变量调用结束后就不存在了。
如果想要函数的局部变量一直存在,那就需要该函数内的变量因被调用而无法销毁。
为了达到这个目的,我们的写法需要如上述闭包用法所要求的那样。
下面有一个例子:
function xm_bank() {
var money = 3000;
function buy(count){
money -=count;
console.log('小明买了'+count +'元的东西后还有' + money+ '元');
}
return buy;
}
var xmBank = xm_bank();
xmBank(30); // 小明买了30元的东西后还有270元
xmBank(70); // 小明买了70元的东西后还有2900元
xmBank(1000); // VM306:5 小明买了1000元的东西后还有1900元
通过闭包,我们实现了对局部变量money的保存。
三、从内存角度谈闭包
JavaScript是解释型语言,逐行解释执行,但在执行之前,还需要经过编译阶段。
编译阶段产生执行上下文,执行上下文主要包括变量环境、词法环境、outer、this。
变量环境是一个栈结构。函数被调用时产生函数执行上下文,函数内定义的变量仅在该函数上下文环境中使用。函数调用结束后该函数上下文环境销毁。
闭包的不同在于,在销毁函数上下文时,发现该函数内变量被返回的函数调用,因为将产生一个函数名(closure)的对象,用来存放该局部变量。
在 JavaScript 中,根据词法作用域的规则,内部函数总是可以访问其外部函数中声明的变量,当通过调用一个外部函数返回一个内部函数后,即使该外部函数已经执行结束了,但是内部函数引用外部函数的变量依然保存在内存中,我们就把这些变量的集合称为闭包。比如外部函数是 foo,那么这些变量的集合就称为 foo 函数的闭包。
当执行到 bar.setName 方法中的myName = "极客邦"
这句代码时,JavaScript 引擎会沿着“当前执行上下文–>foo 函数闭包–> 全局执行上下文”的顺序来查找 myName 变量:
由于没有“人”知道foo(closure)内的变量什么时候会被使用,所以V8垃圾回收机制无法自动回收。。
我们也可以接助Chrome 的“开发者工具”看下:
刚才我们说了,函数产生的闭包对象不会被V8引擎自动收集,如果引用闭包的函数是个全局变量,那么直至页面销毁才会被回收;如果引用闭包的函数是个局部变量,判断闭包这块内容如果已经不再被使用了,那么 JavaScript 引擎的垃圾回收器就会回收这块内存。
这就是为什么我们说,闭包会造成内存泄漏的原因。