【JavaScript】闭包以及原理解析

重学JavaScript03----- 闭包


文章目录

  • 重学JavaScript03----- 闭包
    • 前言
    • 闭包的定义
    • 闭包的理解
    • 闭包的访问过程
    • 闭包在JS引擎中的执行过程
      • 1.预解析阶段
      • 2、执行 var add1 = foo() 函数
      • 3、foo函数出栈
      • 4、执行下一个函数add1()
      • 5、执行下一个函数add1()
      • 6、执行 i = 10086
      • 7、后续执行
    • 闭包的内存泄露
      • 解决

前言

闭包JavaScript中一个非常容易让人迷惑的知识点,本文会从闭包的概念和执行过程入手,详细的剖析闭包的原理。

注意:如果不了解js引擎的运行原理,可以查看上一章内容【V8引擎】JavaScript变量提升

闭包的定义

一个函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包(closure);
也就是说,闭包让你可以在一个内层函数中访问到其外层函数的作用域;
JavaScript 中,每当创建一个函数,闭包就会在函数创建的同时被创建出来;

闭包的理解

一个普通的函数function,如果它可以访问外层作用于的自由变量,那么这个函数就是一个闭包;
从广义的角度来说:JavaScript中的函数都是闭包;
从狭义的角度来说:JavaScript中一个函数,如果访问了外层作用于的变量,那么它是一个闭包;

闭包的访问过程

我们看下面的闭包代码,我们可能会产生疑问。
我们可以取消debugger注释,根据步骤和图示
一步步查看闭包在浏览器中的运行过程,方便了解闭包的原理。

//debugger
function foo() {
	var i = 1
	return function () {
		console.log(i++)
	}
}

var add1 = foo()
add1()//1
add1()//2
i = 10086
add1()//3
add1()//4

foo 函数中的变量 i 为什么一直在递增?函数执行完出栈后变量不是应该被销毁了吗?
为什么会有这么奇怪的现象呢?
正常情况下,我们的 foo 函数执行完毕出栈后, AO 对象会被释放,里面的变量也会被回收;
但是因为我们内层的匿名函数中有作用域引用指向了这个 AO 对象 ,所以它一直不会被释放掉;
下面我们就从JS引擎的运行原理来深度剖析闭包这个有趣的现象!

闭包在JS引擎中的执行过程

1.预解析阶段

  • GEC全局执行上下文中,JS引擎通过预解析会把foo内存地址window等属性存入GO对象

  • add1 变量因为 var 声明提升变成了 undefined

  • 由于没有用关键字声明变量 i = 10086 不会被预解析

Tip : 由于变量声明自带不可删除属性,比如var add1 = foo() 跟 i= 10086,前者是变量声明,带不可删除属性,因此无法被删除;后者为全局变量的一个属性,因此可以从全局变量中删除。
如果是严格模式下 不使用关键字声明变量 例如 i =10086 会直接报错!
【JavaScript】闭包以及原理解析_第1张图片
【JavaScript】闭包以及原理解析_第2张图片

2、执行 var add1 = foo() 函数

JS引擎执行到 foo()时,会根据函数体创建一个FEC函数执行上下文,并压入
add1还是undefined,要等foo()函数执行完return
【JavaScript】闭包以及原理解析_第3张图片

【JavaScript】闭包以及原理解析_第4张图片
foo() 函数执行完后,return 了一个匿名函数出来,这时候 var add1 变量就能指向匿名函数对象的地址了
foo函数会生产自己的ao对象,ao对象存储着定义的形参变量
【JavaScript】闭包以及原理解析_第5张图片

3、foo函数出栈

foo函数执行完了会自动出栈
这时候我们发现AO对象生成以后被匿名函数0xb00的父级作用域引用了,指针一直指着AO对象无法销毁
【JavaScript】闭包以及原理解析_第6张图片

4、执行下一个函数add1()

因为add1内部没有形参和定义的变量所以它的AO对象一直是空的
那么匿名函数i++i因为在自身AO对象中找不到,它就会通过父级作用域往上查找,匿名函数i 此时就变成了 parentScope (父级作用域)的i
即:变成了 AO对象(foo)(0x200) i++
【JavaScript】闭包以及原理解析_第7张图片

【JavaScript】闭包以及原理解析_第8张图片

5、执行下一个函数add1()

由于上一个add1函数打印i++所以这一次的foo函数中AO对象已经变成了2
console.log(i++)
结果当然是 2
再执行 ++
i 变成了 3,但是还没有打印
【JavaScript】闭包以及原理解析_第9张图片

6、执行 i = 10086

这时候i会被赋值 10086

【JavaScript】闭包以及原理解析_第10张图片

7、后续执行

后面的两个add1()/add1()函数执行和第四第五步是同理的就不重复演示了
AO对象中的i会因为后续执行最终变成数字5,而且一直被指针引用着无法释放。

闭包的内存泄露

经过一个例子我们发现闭包的缺点,就是成这些内存都是无法被释放的;
所以我们经常说的闭包会造成内存泄露,其实就是刚才的引用链中的所有对象都是无法释放的;

解决

将变量add1设置为null,因为当add1设置为null时,就不在对函数对象 0xb00 有引用,那么对应指向AO对象地址的0x200指针就消失了,它们会被GC(垃圾回收机制)销毁掉
所以解决方法是在退出函数之前,将不使用的变量全部删除。

你可能感兴趣的:(js,javascript,前端)