理解闭包之前,需要先了解函数作用域,作用域链,垃圾回收机制
一、函数作用域
闭包在《权威指南》,第8章函数这一章进行了讲解,在《你所不知道的js》同样也是放在讲解了作用域之后的第一部分第5章,来讲解闭包。
《你所不知道的js》讲解闭包这一章的名字值得深思——作用域闭包,从这个名字应该就能猜到,闭包与函数作用域密不可分。
函数作用域:在函数内声明的所有变量可以在函数体内被查找到,也可以被该函数嵌套的任何函数体内查找这些变量。
查找规则是:从运行时所处的作用域开始,由内而外,逐级向外查找。
如图1区域声明的变量为全局变量,_i在整个2区域都可被查找到。c_fnc被调用时,在3区域没有找到变量_i,转而在2区域查找,并且找到了这个变量,然后做输出处理。
二、作用域链
上一节说到,变量的查找遵循由内而外,逐级查找的规则。这些逐级查找的一系列对象就存在于作用链上。
2.1.什么是作用域链
作用域链是一个对象列表。不如变量x查找时,从链上的第一个对象开始查找这个对象是否有一个x属性,依次查找每个对象,如果找到就用这个对象的x的值,如果作用域链上所有对象都查找完成都没有找到,会抛出引用错误异常。
比如:
上面是一段不包含任何函数定义的代码,这段代码运行时与之关联的作用域上只有一个全局对象。
函数foo调用时,与foo函数关联的作用域链上有2个对象,第一个对象是包含foo函数内部_c的局部变量,第二个是全局对象。
2.2 作用域链是什么时候存在的?
在函数调用时,就会生成一条作用域链,并且被保存。
函数调用时,会创建一个新的对象(也可以理解为创建了一个活动记录或者执行上下文)。
这个新的对象包含:
2.3 示例代码:
上面代码作用域链图解如下:
三、垃圾回收机制
3.1、概述
c语言中,在内存使用完成后需要程序员手动释放内存。js中提供了自动内存管理的能力,减轻了程序员这部分的工作量。
主要通过引用计数的方法找到不再使用的内存,被引擎释放掉,这就是垃圾回收机制。
在2.3的代码中,foo()执行完成后,新创建的第一个对象,由于不存在其他变量对它的引用,所以函数foo被执行完成后,第一个对象会被从作用域中删除(垃圾回收)。
第一个对象里包含的局部变量_c和作用域链foo_child也会随之被删除。
在foo函数体外部再想得到foo函数局部变量_c已然不可能。
3.2、影响
内存不能及时释放,造成内存泄露
四、闭包
由垃圾回收机制可知,只要想办法在foo函数执行完成后,依然保留foo函数作用域链的第一个对象就能将局部变量_c保留下来。
由第一部分作用域可知,js遵循词法作用域,函数体内的子函数可以获取父函数的变量。
实例代码:
function foo(){
let _c = "Aphelios";
function foo_child(){
let _a="Tresh";
console.log(_a + "---"+_c);
}
return foo_child;
}
var result=foo();
result();//"Tresh---Aphelios"
变量引用图解:
在上述图中,foo函数执行完成后,返回了一个函数,被保存在全局变量result上。外部变量result指向子函数foo_child,foo_child又存在对父函数局部变量_c的引用。
这样包含局部变量_c的第一个对象就不会被垃圾回收(即第一个红框)。
result拉着foo_child,foo_child又拉着_c,_c又是新对象的一个属性。新对象不能被垃圾回收。
这样,即使foo函数执行完成,依然能在foo函数体外通过闭包,获取它的局部变量。
五、总结
父函数(foo)执行时,本来父函数的局部变量(_c)只能在父函数体内或者嵌套函数体内被查找到,在父函数执行完成后,父函数的局部变量应该被垃圾回收,不再存在内存中。但是由于闭包的存在,使得父函数执行完成后,父函数体外存在变量(result)对嵌套函数(foo_child)的引用,嵌套函数(foo_child)不会被垃圾回收,嵌套函数体内变量绑定的对象(第4部分图中的第一个对象)也不会被垃圾回收。
以上,后续会在继续完善内容