JavaScript中对闭包的理解
概念
一个函数和它的周围状态的引用捆绑在一起构成闭包。
闭包可以让内部函数访问外部函数。
在JavaScript中,每当函数被创建时,就会在函数生成时创建一个新的闭包。
作用
让子函数能够使用父函数的变量或函数,将其生命周期延长。
让其变量或函数能够一直存在,在外部可以访问函数内部的值
理解
初步理解
function father() {
var a = 10
return function child() {
console.log(a) // 10
}
}
// 就相当于 inside = funcion child(){...}
var inside = father()
inside()
我们将上面的代码拆解来进行讲解:
- 我们在
father
函数中定义了一个变量和函数child
,并将函数child
返回了; - 在函数
child
的内部创建了一个输出语句来将变量a
进行输出; - 在函数外部,让变量
inside
接收了father
函数; - 就相当于把函数
father
内部的child
函数赋值给了inside
变量; - 之后执行
inside
,就相当于执行了函数内部的child
函数; - 但在
child
函数内部并没有变量a
,那么它是怎么输出变量a
的值的呢; - 其实它就是到它的上级作用域
father
中找到的; - 如果没有找到的话,它就会继续向上级作用域寻找,直到找到全局作用域为止。
像上面这种现象就是闭包,函数嵌套着函数,内部函数使用着外部函数的某些变量。
进阶理解
function sum() {
for (var i = 0; i < 6; i++) {
setTimeout(function () {
console.log(i)
}, 4000)
}
}
sum()
在上面的代码中我们可以发现在一个循环中定义了一个定时器,那么它的输出结果是什么呢?
输出结果:
为什么不是我们想象中的输出:0,1,2,3,4,5 呢?
下面我们就来解释:
- 这里我们定义了一个
for
循环和定时器; - 而定时器又是异步执行的,咱们的
for
循环执行又是非常迅速的; - 这样导致的结果就是,
for
循环很快的执行完了,此时的i=6
,不满足条件就退出了循环,而这是的定时器还久久未执行; - 当定时器执行的时候就只有从
i=6
这个条件开始重复输出6次了。
那我们如何解决这个问题呢?
其实很简单,我们引入闭包,从而来保存变量 i
不被销毁。
如下所示:
匿名函数
让定时器成为了一个闭包,使其 i
得到了保存,没有被销毁。
function sum() {
for (var i = 0; i < 6; i++) {
// 匿名函数自调用
// x 的值就是 i
(function (x) {
setTimeout(function () {
console.log(x)
}, 4000)
})(i)
}
}
sum()
使用ES6中的let
在ES6中新增了 let
,它可以使定义的变量成为块级作用域。
let
是特别适合在循环中使用的。
这里是让for
循环成为了块级作用域,让内部一级一级的执行,从而保证了 i
不被销毁。
function sum() {
for (let i = 0; i < 6; i++) {
setTimeout(function () {
console.log(i)
}, 4000)
}
}
sum()
内存泄露
在使用闭包时,会导致变量一直存在;
这时候我们就需要解除对象的引用;
也就是将对象设置为null,;
来让其内存在适当时候被回收,从而加快浏览器的访问速度。