JavaScript闭包深度解析:原理、机制与应用场景

作者:木棉时间:2026-10-29 18:23:19浏览:427

本文深入解析JavaScript闭包的定义、内部运行机制及垃圾回收原理。通过详解执行环境、活动对象与作用域链,阐明闭包如何保护变量安全并维持内存状态,同时介绍其在实现私有属性和方法中的核心应用场景,帮助开发者写出更优雅的代码。

什么是闭包?

一、什么是闭包?

官方定义指出:闭包是一个包含众多变量及其绑定环境的表达式(通常为函数),这些变量也是该表达式的一部分。这一定义较为学术,通俗而言,JavaScript中所有函数本质上都是闭包。然而,在实际开发中,我们通常关注的是嵌套函数所产生的强大闭包。参考以下代码示例:


function a() { 
 var i = 0; 
 function b() { alert(++i); } 
 return b;
}
var c = a();
c();

该代码具备两个显著特征:

1. 函数b嵌套定义在函数a内部;

2. 函数a的返回值是函数b。

其引用关系如图所示:

什么是闭包?

当执行var c=a()后,变量c指向了函数b。随后执行c()时,会弹出窗口显示变量i的值(首次为1)。这段代码创建了一个闭包,原因在于函数a外部的变量c引用了函数a内部的函数b。

结论是:当函数a的内部函数b被函数a外部的变量引用时,便创建了一个闭包。

进一步阐释,闭包是指在构造函数体内定义另一个函数作为目标对象的方法,而该方法反过来引用外层函数体中的临时变量。这使得只要目标对象存在,其方法就能保持,从而间接保留构造函数调用时的临时变量值。即使构造函数调用结束且变量名消失,目标对象的方法仍能访问该值,且每次调用构造函数生成的新对象和方法拥有独立的临时变量。

闭包的作用

二、闭包有什么作用?

简而言之,闭包的作用在于防止JavaScript垃圾回收机制(GC)在函数a执行返回后回收其资源,因为内部函数b的执行依赖于函数a中的变量。这是对闭包作用直观但不完全严谨的描述,理解闭包需要循序渐进。

在上述示例中,由于闭包的存在,函数a返回后,其内部的变量i始终存在于内存中。因此,每次执行c(),i都会自增1后通过alert输出。

反之,如果函数a返回的不是函数b,情况将完全不同。若a执行完后,b未被返回给a的外部,仅被a引用,而a又被b引用,两者互相引用且不被外界引用,则函数a和b将被GC回收(关于JavaScript垃圾回收机制将在后文详述)。

闭包内的微观世界

三、闭包内的微观世界

要深入理解闭包及函数a与嵌套函数b的关系,需引入几个核心概念:函数的执行环境(execution context)、活动对象(call object)、作用域(scope)以及作用域链(scope chain)。以函数a从定义到执行的过程为例进行阐述。

  • 当定义函数a时,JavaScript解释器会将函数a的作用域链(scope chain)设置为定义a时所在的“环境”。若a为全局函数,则scope chain中仅包含window对象。
  • 当执行函数a时,a会进入相应的执行环境(execution context)。
  • 在创建执行环境过程中,首先为a添加一个scope属性,即a的作用域,其值为第一步中的scope chain。即a.scope等于a的作用域链。
  • 接着,执行环境创建一个活动对象(call object)。活动对象是一个拥有属性的对象,无原型且无法通过JavaScript代码直接访问。创建完活动对象后,将其添加到a的作用域链最顶端。此时,a的作用域链包含两个对象:a的活动对象和window对象。
  • 随后,在活动对象上添加一个arguments属性,用于保存调用函数a时传递的参数。
  • 最后,将函数a的所有形参和内部函数b的引用添加到a的活动对象上。在此步骤中,完成了函数b的定义,因此如同第3步,函数b的作用域链被设置为b被定义的环境,即a的作用域。

至此,函数a从定义到执行的步骤完成。此时a将函数b的引用返回给c,由于函数b的作用域链包含对函数a活动对象的引用,b可以访问a中定义的所有变量和函数。因函数b被c引用且依赖函数a,故函数a在返回后不会被GC回收。

当函数b执行时,亦会经历上述步骤。因此,执行时b的作用域链包含三个对象:b的活动对象、a的活动对象和window对象,如下图所示:

什么是闭包?

如图所示,当在函数b中访问一个变量时,搜索顺序如下:

  • 首先搜索自身的活动对象,若存在则返回;若不存在,则继续搜索函数a的活动对象,依次查找,直到找到为止。
  • 若函数b存在prototype原型对象,则在查完自身活动对象后,先查找自身的原型对象,再继续查找。这就是JavaScript中的变量查找机制。
  • 若整个作用域链上均无法找到,则返回undefined。

小结:本段提及两个重要概念:函数的定义与执行。文中指出,函数的作用域在定义时已确定,而非在执行时确定(参考步骤1和3)。通过一段代码说明此问题:


function f(x) { 
 var g = function () { return x; }
 return g;
}
var h = f(1);
alert(h()); 

该代码中变量h指向了由g返回的f中的匿名函数。

假设函数h的作用域在调用alert(h())时确定,此时h的作用域链为:h的活动对象->alert的活动对象->window对象。假设函数h的作用域在定义时确定,即h指向的匿名函数在定义时已确定作用域,则执行时h的作用域链为:h的活动对象->f的活动对象->window对象。若第一种假设成立,输出值为undefined;若第二种假设成立,输出值为1。

运行结果证实了第二种假设正确,说明函数的作用域确实在定义时确定。

闭包的应用场景

四、闭包的应用场景 保护函数内的变量安全。以最开始的例子为例,函数a中的变量i仅函数b可访问,无法通过其他途径访问,从而保护了i的安全性。

在内存中维持一个变量。如前例,由于闭包,函数a中的i一直存在于内存中,因此每次执行c(),i都会自增1。通过保护变量安全实现JavaScript私有属性和私有方法(外部无法访问)。私有属性和方法在Constructor外部无法被访问。


function Constructor(...) { 
 var that = this; 
 var membername = value; 
 function membername(...) {...}
}

以上三点为闭包最基本的应用场景,许多经典案例均源于此。

Javascript的垃圾回收机制

五、Javascript的垃圾回收机制

在JavaScript中,若一个对象不再被引用,则该对象会被GC回收。若两个对象互相引用且不再被第三方引用,则这两个互相引用的对象也会被回收。因为函数a被b引用,b又被a外部的c引用,这就是函数a执行后不会被回收的原因。

六、结语

理解JavaScript闭包的解释和运行机制,才能写出更为安全和优雅的代码,希望对大家的学习有所帮助。

作者最新文章