一、概述
闭包,日常开发中看起来很少使用,但实际我们又经常在日常开发里无形使用,而且面试也经常被提及,很多同学开发好几年可能对闭包的理解就是只能说出个大概概念,今天咱们就来捶捶闭包。
二、浏览器的垃圾回收机制
在讲闭包之前,咱们先提及一下和闭包相关的知识点,有助于我们更好的理解闭包的产生,这个知识点就是浏览器的垃圾回收机制。由于我们的主角是闭包,所以浏览器的垃圾回收机制只简单的讲一下概念。
垃圾回收概念:
浏览器中会有一个周期性运行的垃圾回收程序,它会去跟踪各个变量的使用情况,当变量不会再被使用时,就会将变量销毁回收,释放内存。
三、闭包
首先我们看下闭包的概念:
能够访问到另一个函数内部作用域的函数。
众所周知,一个函数内部的作用域就是个局部作用域,只有函数内部或函数的子函数才能访问,看示例↓↓↓
function Fn1(){
let a = 'a值';
console.log('Fn1内部输出--->',a); //Fn1内部输出---> a值
function Fn2(){
console.log('Fn2内部输出--->',a); //Fn2内部输出---> a值
};
Fn2();
};
Fn1();
console.log(a); //Uncaught ReferenceError: a is not defined
遵循作用域链查找的原理,由上至下,Fn1内部和Fn2内部都能读取到变量a的值,Fn1外部读取不到a变量。而闭包的概念是在任何作用域下都能访问到Fn1函数中的变量a,如何使其产生闭包呢?此时我们要借助一些手段,看下面示例↓↓↓
function Fn1(){
let a = 'a值';
function Fn2(){
console.log(a)
};
return Fn2;
};
let Fn3 = Fn1();
Fn3(); //a值
上面例子里,我们在Fn2中使用Fn1中的变量,然后将Fn2表达式返出去赋值给Fn3,然后执行Fn3,这样我们在Fn1内部作用域之外访问到了Fn1中的变量,这就是闭包现象。
闭包产生的原因
众所周知,浏览器中运行着一套垃圾回收程序,这个程序会定期的运行,去回收不会再被使用的变量所占的内存。所以正常来说,一个函数执行结束且之后不会再被使用,其内部变量以及它本身将会被垃圾回收程序销毁并释放内存,此处Fn1在执行后我们期望的结果就是它到此为止应该被销毁了,可是此时它的执行结果被赋值给Fn3了,跟Fn3扯上了关系,明面上Fn1之后不会再执行了,但由于它的内部和Fn3有着联系,所以Fn1在执行完并不会被销毁,这就是闭包产生原因。无图无真相,看下面的示例↓↓↓
闭包的产生形式:
闭包产生的形式不仅仅一定要像上面例子一样,只要函数内部发生了函数类型的值传递,就有可能会产生闭包,我们来看看其他闭包产生的形式:
function Fn3(fn){
fn();
}
function Fn1(){
let a = 'a值';
function Fn2(){
console.log(a)
};
Fn3(Fn2); //函数类型的值传递
}
Fn1();
let Fn3;
function Fn1(){
let a = 'a值';
function Fn2(){
console.log(a)
};
Fn3 = Fn2; //函数类型的值传递
}
Fn1();
Fn3();
闭包的实际使用场景
闭包其实在我们的开发中无处不在,只是我们很少去感知,比如setTimeout,setInterval,ajax请求回调等,凡是一切用到回调函数的地方,我们都可能用到过闭包。
总结
- 能在任何作用域下,访问到某个函数内部的变量,被称之为闭包现象
- 闭包之所以会产生,是因为函数内部与其他作用域建立了引用关系,导致内存无法释放,所以在其他作用域中依然可以访问到函数中的变量。