这是我参与8月更文挑战的第八天,活动详情查看:8月更文挑战
闭包是前端面试中的常考点,也是我们必须掌握和合理运用的点,今天就一起来学习下闭包吧。
什么是闭包?
先来看看闭包的定义。在《JavaScript高级程序设计第四版》(红宝书)中,关于闭包是这样描述的: 闭包指的是那些引用了另一个函数作用域中变量的函数。
光看定义很难透彻的理解闭包。理解闭包需要一些前置知识,首先,我们需要知道JavaScript使用的是词法作用域,也就是说函数在执行时使用的定义函数是生效的变量作用域,而不是调用函数是生效的变量作用域。而为了实现这种词法作用域,JavaScript函数对象的内部状态不仅要包括函数自身的代码,还要包括对函数定义时所在作用域的引用。这种函数作用对象与作用域(即一组变量绑定)组合起来解析变量的机制,在计算机科学文献中被称之为闭包。
按照这个定义,JavaScript中的所有函数都是闭包。但是由于大多数的函数定义和调用都在同一个作用域内,所以闭包的存在无关紧要。我们在面试和交流中说的JavaScript的闭包特指的是定义函数与调用函数的作用域不同的时候。最最常见的情形就是一个函数返回了在它内部定义的嵌套函数。
理解闭包
要理解闭包,需要先回顾嵌套函数的词法作用域规则。
let a = "global a"; //声明全局变量a
function testa(){
let a = "local a"; //声明局部变量a
function f(){
return a; //返回当前作用域a的值
}
return f();
}
testa() //返回"local a"
在上面的代码里,testa()这个函数干了三件事情
- 声明了一个局部的变量a
- 定义了一个函数用来返回局部变量a的值
- 调用了该函数 这样看起来很好理解,调用teata()就该返回"local a",如果稍加改动呢?
let a = "global a"; //声明全局变量a
function testa(){
let a = "local a"; //声明局部变量a
function f(){
return a; //返回当前作用域a的值
}
return f;
}
testa()();
现在testa()函数里声明的函数并没有在testa()里直接调用,而是在全局作用域里进行了调用。这时候可能会有些同学觉得此时会返回"global a"。但是实际上在这里,依然会返回"local a",哎 没想到吧 这就是闭包!
让我们来回顾下词法作用域的基本规则:JavaScript函数是使用定义时的作用域来执行的。在定义的嵌套函数f()的作用域中,变量a的绑定的值是"local a",这个绑定无论这个f()函数在哪里执行,在f()函数执行的时候都是有效的。这就是闭包的本质:它会捕获自身定义所在外部函数的局部变量(及参数绑定)。所以我们经常看到一些文章用这样一个比喻形容闭包:把函数理解为一个人,当这人生下来的时候(函数创建时),也附赠了一个背包(闭包),这个背包包括了家庭环境(词法作用域)。还是比较形象的。
闭包的作用和缺点
作用1: 隐藏变量,避免全局污染
作用2: 可以读取函数内部的变量
同时闭包使用不当,优点就变成了缺点:
缺点1: 导致变量不会被垃圾回收机制回收,造成内存消耗
缺点2: 不恰当的使用闭包可能会造成内存泄漏的问题
这里简单说一下,为什么使用闭包时变量不会被垃圾回收机制收销毁呢,这里需要了解一下JS垃圾回收机制;
JS规定在一个函数作用域内,程序执行完以后变量就会被销毁,这样可节省内存;
使用闭包时,按照作用域链的特点,闭包(函数)外面的变量不会被销毁,因为函数会一直被调用,所以一直存在,如果闭包使用过多会造成内存销毁