摘要:闭包的出现正好结合了全局变量和局部变量的优点。这就是闭包的一个使用场景保存现场。
前言
什么是闭包,其实闭包是可以重用一个对象,又保护对象不被篡改的一种机制。什么是重用一个对象又保护其不被篡改呢?请看下面的详解。
作用域和作用域链注意
理解作用域和作用域链对理解闭包有非常大的帮助,所以我们先说一下作用域和作用域链
什么是作用域
作用域表示的是一个变量的可用范围、其实它是一个保存变量的对象
为什么要使用作用域
避免不同范围的变量互相干扰
作用域包含了哪两种
1、全局作用域
在JavaScript中的全局作用域其实就是windows
优点:可重复使用,随处可用
缺点:会造成全局污染
2、函数作用域
临时创建的活动对象AO(Activation Object)、该对象包含了函数的所有局部变量、命名参数、参数集合以及this,当运行时上下文被销毁、活动也会被销毁(闭包形成的原因其实因为就是因为活动对象被引用着无法被销毁而导致的,详细的请继续往下看)
优点:不污染全局
缺点:不可重复使用、仅在函数内可以使用
程序执行的四个阶段
我以下面一段代码解释一下程序执行的几个阶段
var age = "21"; function myAge(){ var age = 0; age++; console.log(age); } myAge(); console.log(age);
第一阶段:在内存中创建执行执行环境栈、把全局对象window压入栈底、在window中声明变量
第二阶段:函数调用时
在执行环境中添加当前函数调用、为本次函数调用创建活动对象AO、根据scope指定运行期活动对象AO的上下文内部对象
第三阶段:函数调用后
函数调用从执行环境栈中出栈、函数作用域AO释放、函数作用域AO中的局部变量也一同被释放
由上面可以看出当一个函数调用完毕它的局部变量就会被释放,下次再次调用时会创建新的局部变量。这就是函数中的局部变量不可重用的原因。
为什么要使用闭包先介绍一下全局变量和局部变量的优缺点
全局变量:可以重用、但是会造成全局污染而且容易被篡改
局部变量:仅函数内使用不会造成全局污染也不会被篡改、不可以重用
从上面可以看出全局变量和局部变量的优缺点刚好是相对的。闭包的出现正好结合了全局变量和局部变量的优点。
何时使用闭包
希望重用一个对象,又保护对象不被污染篡改时
弄清楚了作用域和作用域链、闭包实现的原理也就很容易弄懂了。
下面请看一段代码和几张图^_^
这是一段闭包的代码,我们又这段代码讲讲闭包的原理
function addAge(){ var age = 21; return function(){ age++; console.log(age); } } var clourse = addAge(); clourse(); clourse(); clourse();
第一阶段:在内存中创建执行执行环境栈、把全局对象window压入栈底、在window中声明变量(和前面是相似的)
第二阶段:
1、在栈中添加addAge的函数调用
2、为addAge函数创建活动对象AO、根据addAge函数的scope可以知道其活动对象指向window
3、window对象中的clourse变量记录着addAge()返回的匿名函数的地址[现在addAge()和clourse变量都可以找到匿名函数和addAge()产生的AO]
第三阶段:
addAge()调用完毕出栈、其对活动对象AO的引用也随之消失。
在作用域和作用域链中举的例子中,活动对象AO会被JS中的垃圾回收机制回收大家还记得嘛^_^,
但是这里和前面是不一样的哦!注意了:匿名函数中的scope引用着活动对象AO、匿名函数的地址也被clourse变量记录着。因此,addAge()虽然出栈了,对它的活动对象的引用也消失了,但是其活动对象被匿名函数的scope拽着、所以无法释放不会被回收。
大家观看蓝色的箭头,其实可以发现、蓝色的箭头已经形成了一个闭环了。
此时,由图也可以看出,活动对象AO只能通过clourse变量来找到。这里形成了一个闭包。保存了addAge()函数中的局部变量,使其可以重复使用,但是又不会造成全局污染。这就是闭包的一个使用场景:保存现场。
至于怎么调用重复使用局部变量,具体过程请看下面两幅图。
第四阶段:
clourse()进栈,产生clouse()的活动对象AO,根据它的scope可以知道它的__parent__指向addAge()产生的活动对象AO。
clouse()执行age++,由于在它自己的作用域里面没有age、于是它会到上一级作用域查找age,它在它的上一级作用域中找到了age,于是对其进行了age++,age从21变成了22。执行console.log(age)输出22。
第五阶段:
clourse()出栈,因为clourse产生的AO没有scope拽着它,因此clourse的AO是可以正常释放的。函数出栈,其AO被JS的垃圾回收机制回收。
clourse变量中的匿名函数中的scope依旧拽着addAge()产生的活动对象AO,于是这个活动对象依旧无法被释放[而且这个AO现在只能被clourse找到、clourse可以重复使用这个AO里面的局部变量age、又不会造成全局污染]
剩下阶段:
代码中还有两个函数还有执行
clourse() clourse()
它的执行和第四阶段和第五阶段的原理是一样的。所以在这里我就不重复画图了。
执行clourse():看第四阶段的图,age从22变成了23,执行console.log(age)输出23、看第五阶段的图
执行clourse(): 看第四阶段的图,age从23变成了24,执行console.log(age)输出24、看第五阶段的图
以上就是对闭包形成过程的图解。也说明了闭包保存现场的作用场景。闭包结合了局部变量和全局变量的优点。可以使变量不污染全局,但是又能对变量进行重用。但是,其实闭包也有有缺点的,它比起普通函数会占用更多的内存。
总的来说,以上就是我对闭包的理解。如果大家发现了什么错误欢迎评论指出,一起交流一起学习一起进步!^_^
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/88976.html
摘要:作用域链所谓作用域链,是由当前环境与上层环境的一系列变量对象组成,它保证当前执行环境对符合访问权限的变量和函数的有序访问。当我们在执行函数的时候,需要的变量,在自己的作用域内找不到,便会顺着作用域链往上找,直到找到全局作用域。 一 作用域相关 作用域是一套规则,用来管理引擎如何查找变量。在es5之前,js只有全局作用域及函数作用域。es6引入了块级作用域。但是这个块级别作用域...
摘要:那其实闭包的原因就是外层函数的作用域对象无法释放其实就是一个函数调用会生成的临时作用域图中可看出其实就是在中的匿名函数,所以他的就指向留下的作用域。 引言 网络上关于作用域及闭包的文章很多,自己对于纯理论知识并不能很快的理解,但自己对于图画有很强的记忆和理解能力,因此决定将此知识点以图画的知识表现出来,加深自身理解的同时如果能帮到正在学习的童鞋就再好不过了 下面我以函数的整个生命周期来...
摘要:我的理解就是还处于被引用状态。内存机制的内存空间分为栈堆其中栈存放变量,堆存放复杂对象。对堆内数据进行复制修改时理解闭包有了前面的铺垫,我们再来看看闭包是怎么回事。这种反常的现象我们就叫它,中文名闭包。这就是闭包形成的原因了。 知识小储备 ECMAScript 的数据有两种类型:基本类型值和引用类型值,基本类型指的是简单的数据段,引用类型指的是可能由多个值构成的对象。Undefined...
摘要:变量对象也是有父作用域的。作用域链的顶端是全局对象。当函数被调用的时候,作用域链就会包含多个作用域对象。当函数要访问时,没有找到,于是沿着作用域链向上查找,在的作用域找到了对应的标示符,就会修改的值。 一、概要 对于闭包的定义(红宝书P178):闭包就是指有权访问另外一个函数的作用域中的变量的函数。 关键点: 1、闭包是一个函数 2、能够访问另外一个函数作用域中的变量 二、闭包特性 对...
摘要:因此,所有在方法中定义的变量都是放在栈内存中的当我们在程序中创建一个对象时,这个对象将被保存到运行时数据区中,以便反复利用因为对象的创建成本通常较大,这个运行时数据区就是堆内存。 上一篇:《javascript高级程序设计》笔记:继承近几篇博客都会围绕着图中的知识点展开 showImg(https://segmentfault.com/img/bVY0C4?w=1330&h=618);...
阅读 2543·2021-09-30 09:48
阅读 2538·2019-08-30 14:10
阅读 2673·2019-08-29 11:22
阅读 1809·2019-08-26 13:51
阅读 2230·2019-08-26 12:02
阅读 2313·2019-08-23 16:06
阅读 3520·2019-08-23 14:06
阅读 1062·2019-08-23 13:56