这是我参与8月更文挑战的第10天,活动详情查看:8月更文挑战
内存角度理解闭包
作用域内的原始类型数据会被存储到栈空间,引用类型则会被存储到堆空间。
引
function foo() {
var myName = "极客时间"
let test1 = 1
const test2 = 2
var innerBar = {
setName:function(newName){
myName = newName
},
getName:function(){
console.log(test1)
return myName
}
}
return innerBar
}
var bar = foo()
bar.setName("极客邦")
bar.getName()
console.log(bar.getName())
首先盘一下执行流程
-
首先先创建全局执行上下文并压入调用栈中
-
全局执行上下文
- 变量环境:bar = undefined foo()...
- 词法环境:空
-
-
执行foo,将返回的innerBar给bar
-
执行前首先会编译foo函数。创建一个空的执行上下文。
-
JavaScript引擎对这内部函数做一次词法扫描,首先是setName函数,发现函数内部引用了foo函数的myName,由于是内部函数引用了外部函数的变量,因此JavaScript引擎判断这是一个闭包,因此就会在堆空间中创建一个“closure(foo)”指向的对象(这是一个内部对象,JavaScript 是无法访问的)
-
接着到另一个内部函数getName,JS引擎接着词法扫描,发现这个内部函数还引用了外部的foo函数的test1,于是JS引擎又将test1添加到了“closure(foo)”对象中,这时候堆中的“closure(foo)”对象包含了myName和test1两个变量。
-
由于 test2 并没有被内部函数引用,所以 test2 依然保存在调用栈中foo执行上下文的词法环境中。
此时的foo执行上下文
-
当return返回对象innerBar之后,foo的执行上下文弹出调用栈,但是由于返回的对象innerBar存在指向其父级作用域的引用,产生了闭包closure(foo),还依然存在在堆空间中。
-
- 所以当执行到
bar.setName("极客邦");bar.getName();
依然可以访问到closure(foo)这个闭包对象里的属性。
总结
产生闭包的核心有两步:第一步是需要预扫描内部函数;第二步是把内部函数引用的外部变量保存到堆中。