深入理解JavaScript闭包:原理、应用与内存机制
本文深入解析JavaScript闭包的核心概念,从变量作用域出发,阐述闭包作为读取内部变量的桥梁机制。详细探讨闭包在保持变量持久化、实现私有属性及方法中的应用,并剖析其背后的执行环境、活动对象及垃圾回收机制,帮助开发者掌握闭包原理以编写更安全的代码。
变量作用域基础
一、变量作用域概述
理解闭包的前提是掌握JavaScript独特的变量作用域机制。
变量作用域主要划分为全局变量与局部变量两类。
JavaScript允许函数内部直接访问全局变量。
var n=999;
function f1(){
alert(n);
}
f1(); // 999
反之,函数外部的代码无法直接访问函数内部的局部变量。
function f1(){
var n=999;
}
alert(n); // error
在函数内部声明变量时必须使用var命令,否则将意外创建全局变量。
function f1(){
n=999;
}
f1();
alert(n); // 999
从外部读取局部变量的方法
二、外部读取局部变量的方法
虽然常规情况下无法从外部获取局部变量,但可通过特定技巧实现。
该方法的核心是在函数内部定义另一个函数。
function f1(){
n=999;
function f2(){
alert(n); // 999
}
}
当函数f2嵌套于f1内部时,f1的所有局部变量对f2可见,反之则不可见,这构成了JavaScript特有的“链式作用域”(chain scope)。
子对象可逐级向上查找父对象的变量,因此父对象变量对子对象可见,但子对象变量对父对象不可见。
若将f2作为f1的返回值,即可在f1外部访问其内部变量。
function f1(){
n=999;
function f2(){
alert(n);
}
return f2;
}
var result=f1();
result(); // 999
闭包的概念与本质
三、闭包定义
前述代码中的f2函数即为闭包的实例。
专业文献对“闭包”(closure)的定义抽象难懂,通俗理解即为能读取其他函数内部变量的函数。
鉴于仅函数内部的子函数能读取局部变量,闭包可简单定义为“定义在函数内部的函数”。
本质上,闭包是连接函数内部与外部的桥梁。
闭包的主要用途
四、闭包的主要用途
闭包主要有两大用途:一是读取函数内部变量,二是使变量值持续保留在内存中。
具体原理可通过以下代码示例理解。
function f1(){
var n=999;
nAdd=function(){n+=1}
function f2(){
alert(n);
}
return f2;
}
var result=f1();
result(); // 999
nAdd();
result(); // 1000
示例中result为闭包f2,两次运行结果分别为999和1000,证明f1的局部变量n始终存在于内存中,未被调用后清除。
因f1是f2的父函数,且f2被赋给全局变量,导致f2常驻内存,进而依赖f1也常驻内存,避免被垃圾回收机制(garbage collection)回收。
代码中“nAdd=function(){n+=1}”未使用var,故nAdd为全局变量,其值为匿名函数(anonymous function)。
该匿名函数本身也是闭包,使nAdd充当setter,允许在外部操作内部局部变量。
使用闭包的注意事项
五、使用闭包的注意事项
1)闭包导致变量常驻内存,消耗大量内存,滥用可能引发网页性能问题或IE内存泄露。解决策略是在退出函数前删除不使用的局部变量。
2)闭包会修改父函数内部变量。若将父函数视为对象、闭包视为公共方法(Public Method)、内部变量视为私有属性(private value),需谨慎操作。
避免随意改变父函数内部变量的值。
闭包运行机制思考
六、思考题解析
理解以下代码的运行结果,即掌握闭包运行机制。
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
return function(){
return this.name;
};
}
};
alert(object.getNameFunc()()); //The Window
JavaScript闭包示例分析
function outerFun()
{
var a=0;
function innerFun()
{
a++;
alert(a);
}
}
innerFun()
原代码错误在于innerFun()的作用域位于outerFun()内部,在外部调用属于非法操作。
修正后的代码体现了闭包的正确用法。
function outerFun()
{
var a=0;
function innerFun()
{
a++;
alert(a);
}
return innerFun; //注意这里
}
var obj=outerFun();
obj(); //结果为1
obj(); //结果为2
var obj2=outerFun();
obj2(); //结果为1
obj2(); //结果为2
闭包的概念解析
当内部函数在定义它的作用域外部被引用时,即创建闭包。若内部函数引用了外部函数的变量,外部函数调用完毕后,这些变量因闭包需求而不会释放。
进一步示例分析
function outerFun()
{
var a =0;
alert(a);
}
var a=4;
outerFun();
alert(a);
输出结果为0,4,因函数内部使用var关键字将a的作用域限制在outFun()内部。
另一段代码示例
function outerFun()
{
//没有var
a =0;
alert(a);
}
var a=4;
outerFun();
alert(a);
输出结果为0,0,原因分析如下。
作用域链是确定变量值的路径术语。执行a=0时,因未使用var,赋值操作沿作用域链至var a=4并修改其值。
深入理解闭包原理
若对JavaScript闭包理解不足,可参考转载文章:(转载:http://www.felixwoo.com/archives/247)
一、闭包定义详解
官方定义:闭包是包含变量及绑定环境的表达式(通常为函数),变量亦是其一部分。该定义过于学术,通俗而言:JavaScript中所有function均为闭包,嵌套function产生的闭包更为强大且常见。示例代码如下:
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外的变量引用时,即创建闭包。
更透彻的解释:在构造函数体内定义目标对象的方法函数,该方法函数引用外层函数体中的临时变量。只要目标对象存在,其方法即可间接保持原构造函数体中的临时变量值。即使构造函数调用结束,临时变量名消失,仍可通过方法访问该值。再次调用构造函数生成新对象和方法,新临时变量对应新值,与旧值独立。
二、闭包的作用
简言之,闭包作用在于函数a执行返回后,阻止JavaScript垃圾回收机制(GC)回收a的资源,因内部函数b的执行依赖a中的变量。此描述虽不严谨,但直观体现了闭包作用,理解需循序渐进。
示例中,因闭包存在,函数a返回后,a中的i始终存在,每次执行c(),i自加1后alert输出。
若a不返回函数b,则a执行完后,b未被外界引用,仅被a引用,a又被b引用,二者互相引用且不被外界引用,将被GC回收。(关于JavaScript垃圾回收机制后续详述)
闭包内部的微观世界
三、闭包内部微观机制
深入理解闭包及函数a与嵌套函数b的关系,需引入以下概念:函数的执行环境(excution context)、活动对象(call object)、作用域(scope)、作用域链(scope chain)。以函数a从定义到执行过程为例阐述。
- 定义函数a时,JavaScript解释器将a的作用域链(scope chain)设置为定义时a所在的“环境”,若a为全局函数,则scope chain仅含window对象。
- 执行函数a时,a进入相应的执行环境(excution context)。
- 创建执行环境时,首先为a添加scope属性,即a的作用域,值为第1步中的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引用,b又依赖a,因此a在返回后不会被GC回收。
函数b执行时亦遵循上述步骤。因此,执行时b的作用域链包含3个对象: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指向f中的匿名函数(由g返回)。
- 假设函数h的作用域在执行alert(h())时确定,此时h的作用域链为:h的活动对象->alert的活动对象->window对象。
- 假设函数h的作用域在定义时确定,即h指向的匿名函数在定义时已确定作用域。则执行时,h的作用域链为:h的活动对象->f的活动对象->window对象。
若第一种假设成立,输出值为undefined;若第二种假设成立,输出值为1。
运行结果证明第2个假设正确,说明函数的作用域确实在定义函数时已确定。
闭包的实际应用场景
四、闭包的应用场景 保护函数内变量安全。以最初例子为例,函数a中i仅函数b可访问,无法通过其他途径访问,从而保护i的安全性。
- 在内存中维持变量。如前例,因闭包,函数a中i一直存在于内存中,故每次执行c(),均给i自加1。
- 通过保护变量安全实现JS私有属性和私有方法(不能被外部访问) 私有属性和方法在Constructor外无法被访问
function Constructor(...) {
var that = this;
var membername = value;
function membername(...) {...}
}
以上3点为闭包最基本的应用场景,许多经典案例均源于此。
JavaScript垃圾回收机制
五、JavaScript的垃圾回收机制
在JavaScript中,若对象不再被引用,则该对象将被GC回收。若两个对象互相引用且不再被第3者引用,则这两个互相引用的对象也将被回收。因函数a被b引用,b又被a外的c引用,故函数a执行后不会被回收。
六、结语
理解JavaScript的闭包是迈向高级JS程序员的必经之路,理解其解释和运行机制方能写出更为安全和优雅的代码。
JS动态构建复杂嵌套JSON对象的方法
2026-11-05 15:41:52
JS负数取余为什么是负数?详解余数与模运算区别
2026-11-05 13:34:41
JS 解压出现乱码或报错怎么解决?
2026-11-05 12:32:38
JS 中 startsWith、indexOf 与正则判断字符串前缀的对比
2026-11-04 22:19:43
JS解析Excel后怎么将数据转换为JSON格式
2026-11-04 20:58:28
