重学JavaScript03----- 闭包
文章目录
前言
闭包是JavaScript
中一个非常容易让人迷惑的知识点
注意:如果不了解js引擎的运行原理,可以查看上一章内容【V8引擎】JavaScript变量提升
闭包的定义
一个函数和对其周围状态(lexical environment
,词法环境)的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包(closure
);
也就是说,闭包让你可以在一个内层函数中访问到其外层函数的作用域;
在 JavaScript
中,每当创建一个函数,闭包就会在函数创建的同时被创建出来;
闭包的理解
一个普通的函数function
,如果它可以访问外层作用于的自由变量,那么这个函数就是一个闭包;
从广义的角度来说:JavaScript
中的函数都是闭包;
从狭义的角度来说:JavaScript
中一个函数,如果访问了外层作用于的变量,那么它是一个闭包;
闭包的访问过程
我们看下面的闭包代码
function foo() {
var i = 1
return function () {
console.log(i++)
}
}
var add1 = foo()
add1()//1
add1()//2
i = 10086
add1()//3
add1()//4
为什么会有这么奇怪的现象呢?
正常情况下,我们的foo
函数执行完毕,AO
对象会被释放;
但是因为我们内层的匿名函数中有作用域引用指向了这个AO
对象,所以它一直不会被释放掉;
闭包在JS引擎中的执行过程
1.预解析
在GEC全局执行上下文中,JS
引擎会把foo
和add1
两个函数的内存地址和window
等属性存入GO
对象
由于没有用关键字声明变量 i = 10086 不会被预解析
tip:由于变量声明自带不可删除属性,比如var i = 1 跟 i= 10086,前者是变量声明,带不可删除属性,因此无法被删除;后者为全局变量的一个属性,因此可以从全局变量中删除。
如果是严格模式下 不使用关键字声明变量 例如 i =10086 会直接报错!
2、执行foo函数
存入到堆内存后
JS引擎执行到 var add1 = foo()
时,会根据函数体创建一个FEC函数执行上下文,并压入栈中,
3、foo函数出栈
foo函数执行完了会自动出栈
这时候我们发现AO对象生成以后被匿名函数0xb00引用了,指针一直指着AO对象无法销毁
4、执行下一个函数add1()
因为add1
内部没有形参和定义的变量所以它的AO对象
一直是空的
那么匿名函数中i++
的i
因为在自身AO对象
中找不到,它就会通过父级作用域往上查找,匿名函数i
此时就变成了parentScope
(父级作用域)的i
即:变成了 AO对象(foo)(0x200) i++
4、执行下一个函数add1()
由于上一个add1
函数打印i++
所以这一次的foo
函数中AO
对象已经变成了2
console.log(i++)
结果当然是 2 了
再执行 ++
i 变成了 3,但是还没有打印
5、执行 i = 10086
这时候i会被赋值 10086
6、后续执行
后面是同理的就不重复演示了
闭包的内存泄露
经过一个例子我们发现闭包的缺点,就是成这些内存都是无法被释放的;
所以我们经常说的闭包会造成内存泄露,其实就是刚才的引用链中的所有对象都是无法释放的;
解决
将变量add1
设置为null
,因为当add1
设置为null
时,就不在对函数对象 0xb00
有引用,那么对应指向AO对象地址的0x200
指针就消失了,它们会被GC
(垃圾回收机制)销毁掉
所以解决方法是在退出函数之前,将不使用的变量全部删除。