闭包(closure)是Javascript语言的一个难点,也是它的特色,很多高级应用都要依靠闭包实现。
要理解闭包,首先必须理解JS作用域[点击查看]
闭包的概念
在百度百科上是这么解释闭包的:
其实这个说法是很笼统的。准确来说,闭包是基于正常的垃圾回收处理机制下的。也就是说,一般情况一个函数(函数作用域)执行完毕,里面声明的变量会全部释放,被JS垃圾回收机制回收。但闭包利用一个技巧,让作用域里面的变量,在函数执行完之后依旧保存没有被垃圾回收机制处理掉。
下面我们先写一个常用的闭包例子:
function person() {
var num = 1
return function () {
return console.log(`刘家军周日在敲代码${num++}`);
}
}
var doSth = person(); // doSth 现在是一个闭包
doSth();
doSth();
输出:
我们来分析一下这一段代码,首先执行var doSth = person();那么person就执行了,但是执行完毕之后, 变量num并没有被回收释放,因为返回值里面还等待使用num,所以此时,person虽然执行了,但是person的变量并没有被释放,在return在等待继续使用这些变量了,这个时候doSth就是一个闭包。
你每执行一次doSth,结果就变化一次,这就是闭包的神奇之处,它改变了JS的内存机制。
然后我们再看看长得很像闭包的形式
function person() {
var num = 1
function doSth() {
console.log(`刘家军周日在敲代码${num++}`);
}
doSth()
}
person();
person();
输出:
如果按照百度上所说的闭包可以读取其他函数内部的变量,person在全局中执行,执行过程中也访问到了num,但他不是闭包,我们来一遍它的执行过程,
函数person执行,执行完之后,再执行的时候,里面的num,doSth函数又重新声明了。那么根本就没有阻止person作用域中的变量被js垃圾回收机制所回收,所以就不能叫闭包,所以闭包要是一个定义在函数内部的函数
闭包的特性
我们从上面的例子可以总结出闭包有3个特性:
1.函数嵌套函数再来看一个经典例子-定时器与闭包:
2.函数内部可以引用函数外部的参数和变量
3.参数和变量不会被垃圾回收机制回收
for(var i = 0; i < 5; i++){
setTimeout(() =>{
console.log(i)
},100)
}
输出:
结果输出5次5,因为js是单线程的,所以在执行for循环的时候定时器setTimeout被安排到任务队列中排队等待执行,而在等待过程中for循环就已经在执行,等到setTimeout可以执行的时候,for循环已经结束,i的值也已经编程5,所以打印出来五个5,如果对这一点还不是很清楚的可以看一下我前面所写的关于JS异步系列的文章
那么我们为了实现输出0,1,2,3,4修改下代码,由于在ES5中没有块级作用域的说法,所以得利用函数自己创建一个作用域(如果把for循环里面的var变成let,也能实现预期结果)
for(var i = 0; i < 5; i++){
(function(j){ // 传入参数j j = i
setTimeout(() =>{
console.log(j)
},100)
})(i)
}
输出:
这哪又用到了闭包我们分析下,for循环每一次都执行一个 匿名自执行函数,每一次变量 i 被当做参数传到匿名自执行函数中去 , 那么这个匿名自执行函数中创建了一个变量,延时定时器里面需要用到这个参数 j ,但是因为js异步操作,被添加到任务队列中, 那么这个遍历 j 就没有被清理 , 就一直被保存着 , 每一个匿名自执行函数都做一样的事情 , 所以这个时候就产生了闭包 , 变量 j 并没有被回收,依然在等待你使用。
闭包的优缺点
从上面所说的我们可以总结下闭包的优缺点
优点
1.保护函数内的变量安全 ,实现封装,防止变量流入其他环境发生命名冲突
2.在内存中维持一个变量,可以做缓存
3.匿名自执行函数可以减少内存消耗
缺点
1.其中一点上面已经有体现了,就是被引用的私有变量不能被销毁,增大了内存消耗,造成内存泄漏,解决方法是可以在使用完变量后手动为它赋值为null;
2.其次由于闭包涉及跨域访问,所以会导致性能损失,我们可以通过把跨作用域变量存储在局部变量中,然后直接访问局部变量,来减轻对执行速度的影响
闭包与this的指向
直接上代码:
var name = "window" // 这是全局的
var obj = {
name: "obj", // 这是局部的
func: function(){
// 这里是对象内的方法,这里this指向obj
return function(){
// 这里是闭包,this指向window,所以this.name就是window.name
return console.log(this.name)
}
}
}
var test = obj.func()
test()
输出:
闭包中的this指向的是window对象,this.name=window. name;
如果要改变闭包中的this有两种方法;
1.因为对象内的方法里的this指向这个对象,所以可以再func里定一个变量self代替this,闭包里this. name改为self. name
2.使用call方法,test.call(obj),不了解call的应用的可以看我之前的文章
关于闭包我就讲到这了,明天我们再详细讲解下js中的this指向
友情链接:点击查看 JavaScript作用域、闭包、this指向系列文章目录
友情链接:点击查看所有文章目录