探索奥秘~闭包
在我学习初期我对闭包的理解也不是很深,最近在学习了js更深入的知识后,对闭包有了更加深入的了解,下面我就跟大家详细解读一“闭包”!
一、首先什么是闭包?
“官方”的解释是:所谓“闭包”,指的是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分。
当一个函数能够记住并访问到其所在的词法作用域及作用域链,特别强调是在其定义的作用域外进行的访问,此时该函数和其上层执行上下文共同构成闭包。
相信很少有人能直接看懂这句话,因为他描述的太学术。我想用如何在Javascript中创建一个闭包来告诉你什么是闭包,因为跳过闭包的创建过程直接理解闭包的定义是非常困难的。看下面这段代码:
function a(){
var i=0;
function b(){
alert(++i);
}
return b;
}
var c = a();
c();
这段代码有两个特点:
1、函数b嵌套在函数a内部;
2、函数a返回函数b。
这样在执行完var c=a()后,变量c实际上是指向了函数b,再执行c()后就会弹出一个窗口显示i的值(第一次为1)。这段代码其实就创建了一个闭包,为什么?因为函数a外的变量c引用了函数a内的函数b,就是说:
当函数a的内部函数b被函数a外的一个变量引用的时候,就创建了一个闭包。
**需要明确的几点:
1、闭包一定是函数对象(wintercn大大的闭包考证)
2、闭包和词法作用域,作用域链,垃圾回收机制息息相关
3、当函数一定是在其定义的作用域外进行的访问时,才产生闭包
4、闭包是由该函数和其上层执行上下文共同构成
二、接下来我们来看下闭包是如何产生的?
现在我假设JS引擎执行到这行代码
let baz = foo();
此时,JS的作用域气泡是这样的:
38412730-97a3fd6e-39bc-11e8-9a53-208d71ca98eb-660x572.png
这个时候foo函数已经执行完,JS的垃圾回收机制应该会自动将其标记为”离开环境”,等待回收机制下次执行,将其内存进行释放(标记清除)。
但是,我们仔细看图中粉色的箭头,我们将bar的引用指向baz,正是这种引用赋值,阻止了垃圾回收机制将foo进行回收,从而导致bar的整条作用域链都被保存下来。
接下来,baz()执行,bar进入执行栈,闭包(foo)形成,此时bar中依旧可以访问到其父作用域气泡中的变量a。
我们借助chrome的调试工具看下闭包产生的过程:
当JS引擎执行到这行代码let baz = foo();时:
38487982-9301fd1a-3c14-11e8-8238-57321d84eb05-660x439.jpg
图中所示,let baz = foo();已经执行完,即将执行baz();,此时Call Stack中只有全局上下文。
接下来baz();执行:
38488086-e0d4349a-3c14-11e8-84af-ea05e7546a43-660x439.jpg
我们可以看到,此时bar进入Call Stack中,并且Closure(foo)形成。
三、闭包的应用场景
1、保护函数内的变量安全。以最开始的例子为例,函数a中i只有函数b才能访问,而无法通过其他途径访问到,因此保护了i的安全性。
2、在内存中维持一个变量。依然如前例,由于闭包,函数a中i的一直存在于内存中,因此每次执行c(),都会给i自加1。
以上两点是闭包最基本的应用场景,很多经典案例都源于此!
闭包的奥妙
闭包,它并不是很神秘,反而是在我们的程序中随处可见,让我们静下心来,品味闭包的味道,走进“闭包”世界,感受它的魅力所在!