JavaScript作用域链与闭包详解及优化
本文详细解析JavaScript中的作用域机制,涵盖全局与局部作用域、作用域链及执行上下文的创建过程。重点探讨闭包的原理及其在循环事件绑定中的常见问题,并通过引入辅助函数的方式演示如何优化闭包,避免变量共享导致的错误结果。
JavaScript作用域基础
作用域
全局作用域
局部作用域
作用域链
执行上下文
活动对象
闭包
闭包优化
JavaScript中引入了一个较为复杂的概念——闭包。要理解闭包,可以从字面意思入手,即“封闭的包”,这与作用域的概念紧密相关。因此,在深入探讨闭包之前,有必要先厘清作用域的基本概念。
作用域(scope)
在程序代码中,变量和函数的可用性并非总是无限制的,其可用范围被称为作用域。作用域的引入提升了程序逻辑的局部性,增强了程序的可靠性,并有效减少了命名冲突的风险。
全局作用域(Global Scope)
在代码的任何位置都能访问到的对象拥有全局作用域。以下几种情况会形成全局作用域:
1. 最外层的函数以及在最外层函数外部定义的变量拥有全局作用域,例如:
代码如下:
var outSide="var outside";
function outFunction(){
var name="var inside";
function inSideFunction(){
alert(name);
}
inSideFunction();
}
alert(outSide); //正确
alert(name); //错误
outFunction(); //正确
inSideFunction() //错误 2. 未使用var声明直接赋值的变量会自动声明为拥有全局作用域,例如:
代码如下:
blogName="CSDN李达" 3. 所有window对象的属性都拥有全局作用域,例如:window对象的内置属性如window.name、window.location、window.top等均属于全局作用域。
局部作用域(Local Scope)
代码如下:
<span style="font-family: SimSun; ">function outFunction(){
var name="inside name";
function inFunction(){
alert(name);
}
inFunction();
}
alert(name); //错误
inFunction(); //错误</span> 作用域链与执行上下文
作用域链(scope chain)
在JavaScript中,一切皆对象,包括函数。函数对象与其他对象一样,拥有可通过代码访问的属性和一系列仅供JavaScript引擎访问的内部属性。其中一个内部属性是作用域,它包含了函数被创建时所在作用域中的对象集合,这被称为函数的作用域链,它决定了函数可以访问哪些数据。
当一个函数被创建后,它的作用域链会被填充为该函数创建时所在作用域中可访问的数据对象。例如以下函数:
代码如下:
function add(num1,num2) {
var sum = num1 + num2;
return sum;
} 在函数add创建时,其作用域链中会包含一个全局对象,该全局对象包含了所有全局变量,如下图所示(注意:图片仅列举了全部变量中的一部分):
由此可见,函数的作用域链是在函数创建时生成的。
执行上下文(Execute context)
函数add的作用域将在执行时被使用,例如:
代码如下:
var total = add(5,10); 当执行add函数时,JavaScript会创建一个Execute context(执行上下文),其中包含了add函数运行所需的所有信息。Execute context也有自己的Scope chain,当函数运行时,JavaScript引擎会首先使用add函数的作用域链来初始化执行上下文的作用域链。
活动对象(Active Object)
随后,JavaScript引擎会创建一个Active Object,这些值按照它们在函数中出现的顺序被复制到运行期上下文的作用域链中,它们共同组成了一个新的对象——“活动对象(activation object)”,该对象包含了函数运行期的所有局部变量、参数以及this等变量。此对象会被推入作用域链的前端,当运行期上下文被销毁时,活动对象也随之销毁。新的作用域链如下图所示:

执行上下文是一个动态概念,在函数运行时创建;活动对象Active Object也是一个动态概念,它由执行上下文的作用域链引用。可以得出结论:执行上下文和活动对象都是动态概念,且执行上下文的作用域链是由函数作用域链初始化的。
在函数执行过程中,每遇到一个变量,都会检索从哪里获取和存储数据。该过程从作用域链头部,即从活动对象开始搜索,查找同名的标识符。如果找到了,就使用这个标识符对应的变量;如果没有,则继续搜索作用域链中的下一个对象。如果搜索完所有对象都未找到,则认为该标识符未定义。在函数执行过程中,每个标识符都要经历这样的搜索过程。
闭包原理与优化实践
闭包(closure)
先来看一个实例,javascript代码:
代码如下:
<script type="text/javascript">
function newLoad(){ //新建页面加载的事件
for (var i = 1; i <=3; i++) {
var anchor = document.getElementById("anchor" + i); //找到每个anchor
anchor.onclick = function () {//为anchor添加单击事件
alert ("you clicked anchor"+i);//给出点击反应
}
}
}
window.onload = newLoad; //把newload事件赋值给页面加载
</script> 前台代码:
代码如下:
<body>
<a id="anchor1" href="#">anchor1</a><br/>
<a id="anchor2" href="#">anchor2</a><br/>
<a id="anchor3" href="#">anchor3</a><br/>
</body> 运行结果:无论点击哪个anchor,总会弹出anchor4,而我们根本就没有anchor4:

当我们加载页面时,javascript中的newLoad函数已经运行完毕,由其中的循环可知,anchor已经赋值为4。但是根据以往的编程经验,局部变量使用完毕就会销毁,但是anchor却没有,显然这里JavaScript采用了另外的方式。 闭包在JavaScript中其实就是一个函数,在函数运行期被创建的。当上面的函数被执行的时候,会创建一个闭包,而这个闭包会引用newLoad作用域中的anchor。下面就来看看JavaScript是如何来实现闭包的:当执行newLoad函数时,JavaScript引擎会创建newLoad函数执行上下文的作用域链,这个作用域链包含了newLoad执行时的活动对象。同时,JavaScript引擎也会创建一个闭包,而闭包的作用域链也会引用newload的活动对象。这样,当newload执行完后,虽然其执行上下文和活动对象都已经释放了anchor,但是闭包还是引用着newload的活动对象,所以点击显示的是“you clicked anchor4”。运行期如图:

闭包优化 既然闭包出现了我们不想看到的结果,我们需要对其进行优化。优化后的javascript(其他不变):
代码如下:
<script type="text/javascript">
function newLoad() { //新建页面加载的事件
for (var i = 1; i <= 3; i++) {
var anchor = document.getElementById("anchor" + i); //找到每个anchor
listener(anchor,i);//listener函数
}
}
function listener(anchor, i) {
anchor.onclick = function () {//为anchor添加单击事件
alert("you clicked anchor" + i); //给出点击反应
}
}
window.onload = newLoad; //把newload事件赋值给页面加载
</script> 运行结果:提示对应的提示信息

结果分析:优化后的结果是因为,我们把声明的变量和单击事件相分离。用以上解释的作用域链来解释:页面加载,先执行listener函数,而listener函数需要anchor变量,在listener函数作用域链中没有,会进入下一级作用域链,即查找newLoad中的anchor。因为在listener中已经确定了哪个anchor单击对应哪个提示信息,所以只是在newload中查找对应的anchor而已,不会等循环完毕产生anchor4。 因为接触javascript时间尚短,理解有误的地方欢迎指正。
JavaScript异步发送HTTP请求时怎么优雅地处理错误
2026-11-05 17:10:44
5款主流JS在线运行工具对比:CodePen、JSFiddle与StackBlitz怎么选
2026-11-05 17:04:04
JavaScript入门书籍避坑:这些书不适合初学者
2026-11-05 16:31:15
JS使用Math.round实现保留两位小数的原理与代码
2026-11-05 15:35:55
JavaScript 原生 Date 对象怎么实现日期加减
2026-11-05 14:30:00
