这是我参与更文挑战的第9天,活动详情查看: 更文挑战
一说到闭包可能大家都会觉得有点难懂,一时想不起闭包到底是什么,有一部分人认为匿名函数就是闭包。在JavaScript高级程序设计中提到闭包指的是那些引用了另一个函数作用域中变量的函数,通常是在嵌套函数中实现的。通俗一点来讲就是子作用域可以访问到父作用域中的变量,但父作用域不可以访问子作用域中的变量,子作用域就形成了一个闭包。
大家都知道,js作用域分为全局作用域和局部作用域。js作用域环境中访问变量的顺序是由内向外的,内部作用域可以访问到当前作用域下的变量,同时也可以访问到包含当前作用域的外部作用域的变量,但是外部作用域不能访问到内部作用域的变量,所以内部作用域中可以闭包一些私有变量,也可以称为闭包私有化。
接下来举个简单的闭包例子:
function outFun(){
var xx = 'xixi'
return function() {
return xx
}
}
var newFun = outFun()
console.log(newFun()) // 输出 xixi
复制代码
可能在开发的过程中下面的例子大家会更加常见
for(var i=1; i<5; i++) {
setTimeout(function(){
console.log(i)
}, 100)
}
复制代码
常理来讲,上面的案例应该会依次输出1,2,3,4,但结果是输出了4次5,那么原因是什么呢?
js是个单线程语言,每次只能执行一个任务,每个任务都是依次执行的,for循环是同步任务,会被直接执行,setTimeout属于宏任务,会被放到宏任务队列里,所以当循环完之后,i已经变成了5,当轮到执行定时器任务时,定时器中每次的输出都只能是5了。
其实我们可以通过闭包来改进一下这段代码的输出结果
for(var i=1; i<5; i++) {
(function(i) {
setTimeout(function(){
console.log(i)
}, 100)
})(i)
}
复制代码
此时代码的输出结果是1,2,3,4,使用闭包保存了变量i,将setTimeout放入了立即执行函数中,将for循环中的循环值i作为参数传递,100毫秒后同时打印出1,2,3,4,那问题来了,如何做到100ms之后依次输出结果呢?
for(var i=1; i<5; i++) {
(function(i) {
setTimeout(function(){
console.log(i)
}, 100)
})(i*100)
}
复制代码
在上面的代码中,相当于同时启动4个定时器,i*100 是为4个定时器分别设置了不同的时间,同时启动,但是执行时间不同,每个定时器间隔都是100毫秒,实现了每隔100毫秒就执行一次打印的效果
总结一下使用闭包的优点与缺点:
优点:
- 保护函数内的变量安全
- 可以做到变量缓存
- 匿名自执行函数可以减少内存消耗
缺点:
- 被引用的私有变量不能被销毁,增大了内存消耗,造成内存泄漏,解决方法是可以在使用完变量后手动为它赋值为null
- 闭包涉及到跨作用域访问,会导致性能损失,我们可以通过把跨作用域变量存储在局部变量中,然后直接访问局部变量,来减轻对执行速度的影响