【JavaScript】闭包

发布于:2022-11-01 ⋅ 阅读:(476) ⋅ 点赞:(0)

重学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引擎会把fooadd1两个函数的内存地址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(垃圾回收机制)销毁掉
所以解决方法是在退出函数之前,将不使用的变量全部删除。

本文含有隐藏内容,请 开通VIP 后查看

网站公告

今日签到

点亮在社区的每一天
去签到