前言
在JavaScript的世界里,闭包是一个既神秘又强大的概念,它常常让初学者感到困惑,却也是高级开发者手中不可或缺的利器。本文将带你走进闭包的世界,以最通俗易懂的方式揭开它的面纱,让你不仅能理解闭包是什么,还能掌握它如何工作以及为何重要。
讲个故事
想象一下,你和朋友们举办了一场派对,派对有一个箱子,用来存放朋友们带了的随身物品,派对进行的很顺利,朋友们玩的很开心。然而欢乐的时光总是那么短暂,派对结束,朋友们陆续离开,你准备丢掉那个用来存放东西的箱子,这时有个朋友打个电话过来说:先别丢了,我东西落了,你手下留情!(剧情需要,不要管朋友咋知道你要丢箱子的),这里,朋友的东西还在箱子里,你可以先把箱子丢了,但你不能着东西全丢了,你得拿个另外的盒子把东西装起来,不能弄丢了,不然朋友回来削你,直到朋友回来把东西取走。
问:什么是闭包?
闭包就像是后面找的那个存放东西的盒子,用来装朋友落下的东西(外部变量),即使派对(外部函数) 已经结束了,朋友(内部函数) 还是能拿回那些“落下”的东西(外部变量)。
在技术术语中,闭包是指有权访问另一个函数作用域中的变量的函数,即使在外部函数已经关闭(执行完毕)的情况下,闭包的核心在于它“记住”了自己诞生时的环境,这使得它能够维持对外部变量的访问,即使那些变量按理说已经超出了生命周期。
如何创造一个闭包
闭包的诞生通常伴随着函数的嵌套和返回。让我们通过一个简单的例子来说明:
function outerFunction(outerVariable) {
return function innerFunction() {
console.log(outerVariable); // 访问外部变量
};
}
const closure = outerFunction('Hello, Closure!');
closure(); // 输出: Hello, Closure!
在这个例子中,outerFunction 返回了一个内部函数 innerFunction。即便 outerFunction 执行完毕,由于 innerFunction 记录了对外部变量 outerVariable 的引用,outerVariable 的值得以保留。这就是闭包的魔力——让数据“活”得比预期更久。
当然,要想理解闭包,咱们还得从别的地方入手,先来看一个例子,可以想想应该输出什么
function bar () {
console.log(a);
}
function foo() {
var a = 100
bar();
}
var a = 200
foo();
咱们看到,全局里面声明了一个变量a和两个函数,可能刚开始会有人这么想,函数bar()是在函数foo()里面调用的,那么不就是用foo() 里面的a值吗,100。但是,这里输出的值是200。
为什么会是200呢?
这里咱们就不能不提作用域链。每个函数在执行时都会创建一个执行上下文,其中包含了函数的变量环境。而作用域链就是这个环境链的串联,它帮助JavaScript引擎确定如何查找变量。每个函数在预编译阶段都会生成自己的作用域,并且拥有一个指向其外层作用域的引用——outer属性,这里要注意,函数声明在哪里,outer属性就会指向哪里。
咱们再来看上面这串代码,全局中声明了两个函数,那么,函数bar()和函数foo()的outer属性都是指向全局的,当函数bar()在函数foo()中被调用时,它会找一个变量a,但是函数bar()里面并没有声明变量a,所以它就要到outer属性所指向的外部也就是全局中找变量a,找到a = 200,则输出200。
小试牛刀
咱们来看下一串代码,思考会输出什么
var arr=[]
for(var i=0;i<3;i++){
arr[i]=function(){
console.log(i);
}
}
arr.forEach(function(item){
item()
})
相信在这里,有的小伙伴没有看仔细的话就会说输出的是0,1,2了,但是这串代码的输出结果为3,3,3
我们思考,上面这串是为什么会输出3,3,3三个3呢?
因为for循环执行完i = 0 i = 1 i = 2后并没有保留下来,数组中传入的值是函数function 也就是arr[0] = function arr[1] = function arr[2] = function,此时,i = 3,是不满足小于3这一条件的,那么就不再执行循环,执行下一行代码了,立即执行函数function打印输出值,将此时的i的值传入到了function中打印,就会出现arr[0] = 3 arr[1] = 3 arr[2] = 3,再被遍历就输出了上面的结果了。
那么咱们有什么办法可以让这串代码的输出结果是0,1,2呢?有人会说,这不是很简单吗,将for语句里的var改成let不就可以了。这样确实可以达成目的,那么有没有别的办法了呢?
这就不得不用到闭包啦
上面的代码会出现三个3是因为前面i的值没有保留下来,咱们让他保留下来不就可以了吗,咱们来代入故事,i = 0, i = 1, i = 2就是朋友落下的东西,函数function就是朋友,那么只要在朋友来之前把他落下的东西装起来等他来拿就不会被削了吧,这不就正好用上闭包了嘛,那么咱们来看看是怎么实现的吧
for(var i=0;i<10;i++){
function foo(){
var j=i
arr[j]=function(){
console.log(j);
}
}
foo()
}
arr.forEach(function(item){
item()
})
闭包的实际应用
闭包的应用广泛,主要体现在以下几个方面:
-
封装和私有变量:在没有类的JavaScript早期,闭包是实现对象私有属性和方法的主要方式。通过闭包,我们可以创建只对内部函数可见的变量,从而保护数据不被外界随意修改。
-
缓存计算结果:利用闭包可以存储中间计算结果,避免重复计算。这对于性能优化尤其有用,比如在处理大量数据或复杂计算时。
-
模块化开发:闭包有助于构建独立的模块,各模块之间互不影响,提高了代码的可维护性和可复用性。
闭包虽然好但也要慎用哦
尽管闭包功能强大,但它也有潜在的副作用——可能导致内存泄漏。想象一下,如果你的家里堆满了朋友们落下的东西,久而久之,空间就会越来越拥挤。同样,如果闭包持续持有对外部变量的引用,而这些变量实际上已经不再使用,那么垃圾回收机制就无法回收这些内存,从而造成内存泄漏。
解决内存泄漏的关键在于适时释放不再需要的闭包或外部变量引用,确保内存得到及时回收。可以通过将变量设为null或在不再需要时解除事件监听等方式实现。
结语
以上就是我对闭包的个人见解啦,有错误的地方还请各位大佬指出,互相学习,如果有帮到你也别忘了点点赞哦。