话接上文,我第一次真正理解了作用域链,那就简单从这方面看看闭包
面试的时候闭包基本上都会被问到,但是其实自己也并没有真正的理解,通常就是简单的说,一个函数调用了另一个函数的变量就形成了闭包,上次在查有关作用域链的资料时,顺便看了一下闭包,这里就简单的记录一下
闭包的调用栈
function foo() {
var myName = "变量1"
let test1 = 1
const test2 = 2
var innerBar = {
getName:function(){
console.log(test1)
return myName
},
setName:function(newName){
myName = newName
}
}
return innerBar
}
var bar = foo()
bar.setName("变量2")
bar.getName()
console.log(bar.getName())
根据代码,当执行到 foo 函数内部的return innerBar这行代码时调用栈的情况,如图
当 foo 函数执行完成之后,其整个调用栈的状态如下图所示:
从上图可以看出,foo执行完成之后,其执行上下文从栈顶弹出了,但是由于返回的 setName 和 getName 中使用了 foo 内部的变量 myName 和 test1,所以这两个变量依然保存在内存中。这像极了 setName 和 getName 方法背的一个专属背包,无论在哪里调用了 setName 和 getName 方法,它们都会背着这个 foo 函数的专属背包。之所以是专属背包,是因为除了 setName 和 getName 函数之外,其他任何地方都是无法访问该背包的,我们就可以把这个背包称为 foo 函数的闭包。
闭包如何使用
当执行到 bar.setName 方法中的myName = "变量2"这句代码时,JavaScript 引擎会沿着“当前执行上下文–>foo 函数闭包–> 全局执行上下文”的顺序来查找 myName 变量
你也可以通过“开发者工具”来看看闭包的情况,打开 Chrome 的“开发者工具”,在 bar 函数任意地方打上断点,然后刷新页面,可以看到如下内容:
从图中可以看出来,当调用 bar.getName 的时候,右边 Scope 项就体现出了作用域链的情况:Local 就是当前的 getName 函数的作用域,Closure(foo) 是指 foo 函数的闭包,最下面的 Global 就是指全局作用域,从“Local–>Closure(foo)–>Global”就是一个完整的作用域链。
闭包是怎么回收的
- 通常,如果引用闭包的函数是一个全局变量,那么闭包会一直存在直到页面关闭;但如果这个闭包以后不再使用的话,就会造成内存泄漏。
- 如果引用闭包的函数是个局部变量,等函数销毁后,在下次
JavaScript 引擎执行垃圾回收时,判断闭包这块内容如果已经不再被使用了,那么JavaScript 引擎的垃圾回收器就会回收这块内存