闭包
JS中闭包的定义
- 这里先来看一下闭包的定义,分成两个:在计算机科学中和在JavaScript中
- 在计算机科学中对闭包的定义(维基百科):
- 闭包(英语:Closure),又称词法闭包(Lexical Closure)或函数闭包(function closures);
- 是在支持 头等函数 的编程语言中,实现词法绑定的一种技术
- 闭包在实现上是一个结构体,它存储了一个函数和一个关联的环境(相当于一个符号查找表);
- 闭包跟函数最大的区别在于,当捕捉闭包的时候,它的 自由变量 会在捕捉时被确定,这样即使脱离了捕捉时的上下文,它也能照常运行
- 闭包的概念出现于60年代,最早实现闭包的程序是 Scheme,那么我们就可以理解为什么JavaScript中有闭包:
- 因为JavaScript中有大量的设计是来源于Scheme的;
- 我们再来看一下MDN对JavaScript闭包的解释:
- 一个函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包(closure);
- 也就是说,闭包让你可以在一个内层函数中访问到其外层函数的作用域;
- 在 JavaScript 中,每当创建一个函数,闭包就会在函数创建的同时被创建出来;
- 理解和总结:
- 一个普通的函数function,如果它可以访问外层作用于的自由变量,那么这个函数就是一个闭包;
- 从广义的角度来说:JavaScript中的函数都是闭包;
- 从狭义的角度来说:JavaScript中一个函数,如果访问了外层作用于的变量,那么它是一个闭包;
闭包的访问和执行过程
function foo() {
var name = "foo"
function bar() {
console.log("bar", name) // bar foo
}
return bar
}
var fn = foo()
fn()
我们由上面的这段代码来解释什么是闭包。
首先在全局解析过程中fn是一个undefined,foo函数指向堆内存中创建好的存放函数对象的空间,然后到执行阶段,执行foo函数,foo函数再进行解析函数中的内容创建AO对象,再执行,name被赋值为foo,bar中存储的是bar函数对象的地址。执行完毕,函数上下文从栈空间中移除。此时fn的值为bar函数的地址,fn()执行bar函数,打印name的值在bar函数的AO对象中找不到,这时会沿着作用域链找到它的上级作用域,找到name打印出来为foo。
这里就形成了一个闭包,按正常来说,在foo函数的函数执行上下文弹出栈以后,它所对应的AO对象也应被删除,但是它却没有删除,因为在bar函数中引用了其中的变量,这个被引用的变量与bar函数一起形成了一个严格意义上的闭包。闭包是在函数创建之初就已经被创建出来了。
当这个闭包被捕捉到时,它的自由变量就已经被确定,即使脱离上下文也能够正常运行。
内存泄漏
在上面的过程中我们注意到一个问题:在所有代码执行完成,foo函数所对应的AO对象没有被删除,这是因为fn它所存储的地址指向了bar的函数对象区域,而在bar的父级作用域又指向了foo的AO对象,这就导致了bar的函数对象和foo的AO对象都没有被清除,如果说下面没有再对foo或者fn函数的调用的话,那这里就造成了内存一直被占用,就造成了内存泄漏。
解决的方法也很简单,只需要手动清除:fn = null