1.前置知识---变量作用域
变量根据作用域的不同分为两种:全局变量和局部变量。
①.函数内部可以使用全局变量。
②.函数外部不可以使用局部变量。
③.当函数执行完毕,本作用域内的局部变量会销毁。
我们都知道,js的作用域分两种,全局和局部,基于我们所熟悉的作用域链相关知识,
我们知道在js作用域环境中访问变量的权利是由内向外的,内部作用域可以获得当前作用域下的变量并且可以获得当前包含当前作用域的外层作用域下的变量,反之则不能,
也就是说在外层作用域下无法获取内层作用域下的变量,
同样在不同的函数作用域中也是不能相互访问彼此变量的,
那么我们想在一个函数内部也有限权访问另一个函数内部的变量该怎么办呢?闭包就是用来解决这一需求的,闭包的本质就是在一个函数内部创建另一个函数。
2.什么是闭包
闭包( closure )指有权访问另一个函数作用域中变量的函数。----- JavaScript高级程序设计
简单理解就是,一个作用域可以访问另外一个函数内部的局部变量,该局部变量所在的函数就是闭包
<script>
//fun这个函数作用域访问了另一个函数fn里面的局部变量num,那么函数fn就是一个闭包函数
function fn() {
let num = 3
function fun() {
console.log(num);
}
fun()
}
let fun = fn()
fn()//3
</script>
3.闭包的三个特性:
①函数嵌套函数
②函数内部可以引用函数外部的参数和变量
案例一:
<script>
function fn() {
let name = 'hsq'
return function() {
return name
}
}
let globalName = fn()
console.log(globalName())//hsq
</script>
在这个案例中,函数fn()返回的是一个匿名函数,这个匿名函数在fn()函数作用域内部,所以它可以获取fn()函数作用域下变量name的值,将这个值作为返回值赋给全局作用域下的变量globalName,这样就实现了在全局变量下获取到局部变量中的值
③参数和变量不会被垃圾回收机制回收
例子:
<script>
function fn() {
let num = 3
return function() {
let n = 0
console.log(++n)
console.log(++num)
}
}
let fun = fn()
fun()// 1 4
fun()//1 5
</script>
一般情况下,在函数fn执行完后,就应该连同它里面的变量一同被销毁。
但在这个例子中,匿名函数作为fn函数的返回值被赋值给了fun,这时候相当于fun=functtion(){
let n=0...},并且匿名函数内部引用着fn里的变量num,所以变量num无法销毁,而变量n是每次被调用时新创建的,所以每次fun执行完后fun就把自己的变量连同自己以前销毁,于是最后就剩下num,于是就产生了内存消耗的问题。
4.闭包的应用
闭包可以用于函数的封装
例如封装一个防抖函数
function debounce(fn,wait){
var timer = null;
return function(){
if(timer !== null){
clearTimeout(timer);
}
timer = setTimeout(fn,wait);
}
}
5.闭包的好处与坏处
1.好处
①保护函数内的变量安全 ,实现封装,防止变量流入其他环境发生命名冲突
②在内存中维持一个变量,可以做缓存(但使用多了同时也是一项缺点,消耗内存)
③匿名自执行函数可以减少内存消耗
2.坏处
①其中一点上面已经有体现了,就是被引用的私有变量不能被销毁,增大了内存消耗,造成内存泄漏,解决方法是可以在使用完变量后手动为它赋值为null;
②其次由于闭包涉及跨域访问,所以会导致性能损失,我们可以通过把跨作用域变量存储在局部变量中,然后直接访问局部变量,来减轻对执行速度的影响