JavaScript 闭包机制深度解析:从经典循环陷阱到 this 指向与内存管理

作者:拾光

时间:2026-10-30

浏览:153

本文深入解析 JavaScript 闭包的核心机制。通过经典的循环点击案例,揭示变量共享引用的陷阱,并对比属性存储与立即执行函数两种解决方案。进一步探讨闭包对 this 对象的影响,阐明通过中间变量保存上下文的方法,并总结闭包在封装私有变量中的实际应用价值。

文章目录

梳理网络上关于闭包的零散概念,旨在系统性地厘清这一核心机制。

闭包的核心定义与经典循环陷阱

闭包的定义:在 JavaScript 中,内部函数始终有权访问其外部函数声明的参数和变量,即便外部函数已经执行完毕并返回(即生命周期结束)。

这是初次接触闭包概念时的典型困惑场景。


<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8"/>
<title>闭包循环问题</title>
<style type="text/css">
p {background:#ccc; width: 300px; height: 100px;}
</style>
</head> 
<body> 
<p id="p0">段落0</p> 
<p id="p1">段落1</p> 
<p id="p2">段落2</p> 
<p id="p3">段落3</p> 
<p id="p4">段落4</p> 
<script type="text/javascript">
for( var i=0; i<5; i++ ) { 
document.getElementById("p"+i).onclick=function() { 
alert(i); //访问了父函数的变量i, 闭包 
};
};
</script> 
</body> 
</html>

若未深入理解闭包机制,直觉上会认为点击各个段落会分别弹出对应的索引号 0、1、2、3、4。然而实际运行结果却是每次点击都弹出数字 5。

针对这一现象,网络上有大量讨论文章提供了多种修复方案。

解决循环变量共享的两种方案

第一种解决思路:将循环变量 i 的值存储到对应 DOM 元素的自定义属性中。


var pAry = document.getElementsByTagName("p"); 
for( var i=0; i< 5; i++ ) { 
pAry[i].no = i; 
pAry[i].onclick = function() { 
alert(this.no); 
} 
};

第二种解决思路:引入一层闭包,将变量 i 作为参数传递给内部函数。


var pAry = document.getElementsByTagName("p"); 
for( var i=0; i< 5; i++ ) { 
pAry[i].no = i; 
pAry[i].onclick = function() { 
alert(this.no); 
} 
};

闭包的内存驻留与地址引用机制

针对此现象,常见解释称“变量 i 以指针或地址形式保存在函数作用域中”。既然涉及底层指针概念,值得进一步探究其内存行为。

探索一:在上述代码逻辑下,所有点击事件均返回数值 10。


(function test() { 
var temp =10; 
for(var i=0; i< 5; i++ ){ 
document.getElementById("p"+i).onclick=function() { 
alert(temp); //访问了父函数的变量temp, 闭包 
}
};
temp=20;
})();

探索二:第一次点击返回 10,后续所有点击均返回数值 20。


(function test() { 
var temp =10; 
for( var i=0; i< 5; i++ ) { 
document.getElementById("p"+i).onclick=function() { 
alert(temp); //访问了父函数的变量i, 闭包 
}
if(i===1){
alert(temp);
}
};
temp=20;
})();

由探索的1、2,可以得出结论:函数内部访问了与函数同级的变量,那么该变量是常驻内存的。访问该变量实质上是访问的是变量的地址;

此外,参考关于“JS 闭包中 this 对象”的论述,继续探讨 this 关键字在闭包环境下的行为。


// js闭包this对象1
var name = 'The Window';
var object = {
  name : 'My Object',
  getNameFunc1 : function(){
    // return this.name;
    console.log(this);//object
    return function(){//闭包,访问的便是全局变量的了,this指windows
     console.log(this);//windows
     return this.name; //The Window
    }
  },
  getNameFunc2 : function(){
     return this.name;//访问的是object
  },
  aa:function(){
   alert(22);
  }
};
alert(object.getNameFunc1()());//弹出“The Window”

闭包环境下的 this 指向问题

问:为何匿名内部函数无法直接获取其外层作用域的 this 对象? 答:每个函数在调用时会自动生成两个特殊变量:this 和 arguments。内部函数在查找这两个变量时,仅能搜索到其自身的作用域链末端(活动对象),因此无法直接访问外部函数中的 this 和 arguments。 但可通过以下代码实现对外部变量(含 this 指向的对象)的访问:


// js闭包this对象2
var name = 'The Window';
var object = {
  name : 'My Object',
  getNameFunc : function(){
   var that = this;
   console.log(this);//输出的是object
   return function(){
    console.log(this);//输出的仍然是Windows
    return that.name;
   };
  }
};
alert(object.getNameFunc()());//弹出“My Object”

关键区别在于将外部函数的 this 对象赋值给局部变量 that。即使外部函数返回,that 依然引用该对象,从而确保内部函数能正确访问并返回预期对象。 既然已详细阐述闭包原理,顺便探讨其实际应用场景;若闭包仅用于制造问题,则其价值大打折扣。

闭包的实际应用场景与价值

观察一个典型的闭包应用示例:


function A(){
 var a=1;
 function B(){
  return a;
 }; 
 return B;
};

var C=A();//C取得A的子作用域B的访问接口
console.log(C());//1 C能访问到B的父级作用域中的变量a 

只要其他作用域能够获取子作用域的访问接口,即可通过该接口访问子作用域父级作用域中的变量。这意味着,若未来需要访问某个函数内部的特定值,闭包将提供极大的便利。

上述代码示例多源自网络资料,此处仅为整理个人理解与阅读过程中的总结。

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章