1.对闭包的理解
先看案例
function Func() {
let count = 0
return function () {
return count++
}
}
var Func_1 = Func()
console.log(Func_1()); //1
console.log(Func_1()); //2
console.log(Func_1()); //3
代码分析
从代码中我们可以看到,Func
函数返回的是一个匿名函数,在该匿名函数中,访问到当前函数的局部变量count
,并执行了处理,形成闭包
Func_1
是Func
的实例,指向的是Func
返回的匿名函数。
当第一次执行Func_1()
的时候,返回1,此时count
变量在该函数中并没有被销毁,而是被存储下来
那么第二次和第三次调用的时候,count
变量仍然能够访问当上次修改后的结果,并可以做出修改,使变量私有化
定义
闭包是指有权访问另一个函数作用域中变量的函数,创建闭包的最常见的方式就是在一个函数内创建另一个函数,创建的函数可以访问到当前函数的局部变量。
简单来说:就是在一个执行上下文中创建的函数,如果访问了这个执行上下文中变量对象的值,那么闭包就产生了
。
2.闭包解决的问题
我们都知道,根据作用域查找规则,外部是不能访问到函数内部数据,如果我们想访问函数内部数据,可以借助函数中的return
:
function f1(){
var num = 10;
return num;
}
var res = f1();
console.log(res); // 10
借助return
可以访问到函数内部数据,但存在一个问题:数据不能被二次访问。因为第二次访问时候是再次调用该函数,函数中的代码才会再次返回,这个我们通过生成随机数可以很好的证明:
function f1() {
var num = Math.random();
return num;
}
var res1 = f1();
var res2 = f1();
console.log(res1 + '\n' + res2);
输出结果如下,我们发现两次的输出结果并不一样:
无论我们怎样执行,两次的随机数结果都不同,这种输出结果显然不好。如果我们想让函数只执行一次,我们该怎么做呢?我们可以在f1
函数中嵌套一个函数,嵌套的内部函数是可以访问f1函数变量的。
function f1(){
var num = Math.random();
function f2(){
return num
}
return f2
}
var f = f1();
var res1 = f();
var res2 = f();
console.log(res1 + '\n' + res2);
此时的输出结果如下:
这就产生了闭包,我们试着分析一下这段代码:
- 全局
f1
函数在0级作用域链上,f1
函数是一个一级链,f1
函数中有一个变量num
,还有一个函数体f2
。 f2
是二级链,通过return将f2
当做一个值返回给f1
函数。f1
函数执行后,将f2
的引用赋值给f
,执行f
函数,输出num
变量。
正常来说,当f1函数调用完毕,其作用域是被销毁的,而通过闭包我们将f2给了f,f2函数内仍然持对num的引用,num仍然存活内存中,延长了内部函数局部变量生命周期。在当f调用,num是可以访问到的。
其实,闭包也就是使用了链式访问技巧,0级链无法访问一级链数据,我们通过间接0级链操作二级链的函数,来访问一级链数据。
闭包解决的问题是:让函数外部访问到函数内部的数据。
3.闭包本质
看下面这段代码,我们来分析闭包是如何产生的:
分析:每个函数的内部都会有一个[[Scopes]],用来存储作用域中的变量,其中包括Global全局变量和自身上下文中的局部变量,一旦闭包产生,生成一个Closure的对象来存储 调用父元素的变量
4.闭包的缺点
闭包也存在着一个潜在的问题,由于闭包会引用外部函数的变量,但是这些变量在外部函数执行完毕后没有被释放,那么这些变量会一直存在于内存中,总的内存大小不变,但是可用内存空间变小了。 一旦形成闭包,只有在页面关闭后,闭包占用的内存才会被回收,这就造成了所谓的内存泄漏。
因此我们在使用闭包时需要特别注意内存泄漏的问题,可以用以下两种方法解决内存泄露问题:
- 及时释放闭包:手动调用闭包函数,并将其返回值赋值为null,这样可以让闭包中的变量及时被垃圾回收器回收。
- 使用立即执行函数:在创建闭包时,将需要保留的变量传递给一个立即执行函数,并将这些变量作为参数传递给闭包函数,这样可以保留所需的变量,而不会导致其他变量的内存泄漏。