本文深入解析JavaScript作用域链的构建过程,详细图解简单函数与闭包在执行时内存中对象的变化。重点阐述闭包如何引用外部活动对象导致内存占用增加,并通过解除引用实现内存回收的完整生命周期,帮助开发者优化JavaScript内存使用。
JavaScript作用域链基础
本文通过具体实例深入解析JavaScript中的作用域链机制,旨在为读者提供清晰的参考与学习思路。
与其他编程语言类似,变量和函数的作用域决定了它们的查找路径。在JavaScript中,理解作用域尤为关键,因为它不仅决定了this关键字的指向,还涉及闭包对外部作用域的访问能力。在JavaScript中,每个函数都是Function对象的实例,该对象包含一个内部属性[[Scope]],该属性仅由JavaScript引擎访问。通过[[Scope]]属性,可以访问函数的作用域链,进而搜索变量和函数,判断它们位于作用域链中的哪一个活动对象中。
简单的作用域链
当函数被创建时,作为Function对象的实例,它拥有[[Scope]]内部属性。该属性指向一个作用域链,默认情况下,这个作用域链至少包含一个全局对象变量。
function compare(value1, value2){
if (value1 < value2) {
return -1;
} else if (value1 > value2) {
return 1;
} else {
return 0;
}
}
var result = compare(5, 10);
上述代码首先定义了一个compare()函数,随后在全局作用域中调用该函数。在创建compare()函数时,其作用域链结构如图所示:

当compare()函数在全局作用域中被调用执行时,JavaScript引擎会创建一个运行时上下文(execution context)的内部对象。一个运行时上下文定义了一个函数执行时的环境。每次函数执行时,其运行时上下文都是独立的,因此多次调用会导致多个运行时上下文的创建与销毁。
每个运行时上下文都拥有自己的作用域链,用于解析变量和函数等标识符。
运行时上下文在函数调用执行时创建,在函数执行完毕后销毁。在创建运行时上下文时,首先会复制被调用函数的[[Scope]]属性中的对象,随后创建一个活动对象(作为变量对象使用)并推入运行时上下文作用域链的前端。对于本例中compare()函数的运行时上下文而言,其作用域链包含两个变量对象:compare()的活动对象(activation object of compare())与全局变量对象(global object)。
对于简单的作用域链,情况如上所述;然而,当涉及闭包时,情况会有所不同。

闭包与内存管理机制
闭包的作用域链
//step1: define createComparisonFunction
function createComparisonFunction(propertyName){
return function(object1, object2){
var value1 = object1[propertyName];
var value2 = object2[propertyName];
if (value1 < value2) {
return -1;
} else if (value1 > value2) {
return 1;
} else {
return 0;
}
};
}
//step2: call createComparisonFunction
var compare = createComparisonFunction("name");
//step3: call compare
var result = compare({name: "Nicholas"}, {name: "Gerg"});
//step4: dereference closure for recycle memory
compare = null;
我们将分以下步骤图解作用域链的变化过程:
step1: 定义createComparisonFunction;

在创建createComparisonFunction函数后,该函数即可被调用,因此内存中保留了一个createComparisonFunction的Function对象; 此时内存中保留的对象包括:
1. Global Object
2. createComparisonFunction对象 & Scope Chain
step2: 执行createComparisonFunction;

在执行createComparisonFunction的过程中,首先会创建createComparisonFunction的运行时上下文对象、ScopeChain以及活动对象;其次,会创建一个闭包(匿名函数)。 函数执行时内存中保留的对象包括:
1. Global Object 2. createComparisonFunction的Function对象 + Scope Chain 3. createComparisonFunction的运行时上下文对象 + Scope Chain 4. createComparisonFunction的活动对象 5. Closure(anonymous)的Function对象 + Scope Chain
在执行完createComparisonFunction之后,createComparisonFunction的运行时上下文对象及ScopeChain会被销毁。然而,由于createComparisonFunction的活动对象被Closure(anonymous)对象的ScopeChain所引用,因此该活动对象不会被销毁。
函数执行完毕后内存中保留的对象包括:
1. Global Object 2. createComparisonFunction的Function对象 + Scope Chain 3. Closure(anonymous)的Function对象 + Scope Chain 4. createComparisonFunction的活动对象
对比step1,step2在执行完毕后,增加了两个对象:
1. Closure(anonymous)的Function对象 + Scope Chain 2. createComparisonFunction的活动对象
这是因为执行createComparisonFunction所产生的闭包被compare变量所引用,而该闭包函数的Scope Chain又引用了createComparisonFunction的活动对象,因此内存中增加了这两个对象。
step3: 执行compare;

在执行闭包(compare)时,首先会创建闭包的运行时上下文对象、ScopeChain以及活动对象,随后执行闭包。
闭包执行时内存中保留的对象包括:
1. Global Object 2. createComparisonFunction的Function对象 + Scope Chain 3. 闭包Closure(anonymous)的Function对象 + Scope Chain 4. createComparisonFunction的活动对象 5. 闭包Closure(anonymous)的运行时上下文 + Scope Chain 6. 闭包Closure(anonymous)的活动对象
执行完闭包Closure(anonymous)之后,闭包Closure(anonymous)的活动对象会被销毁,同时闭包Closure(anonymous)的运行时上下文及Scope Chain也会被销毁。
闭包执行完毕后内存中保留的对象包括:
1. Global Object 2. createComparisonFunction的Function对象 + Scope Chain 3. 闭包Closure(anonymous)的Function对象 + Scope Chain 4. createComparisonFunction的活动对象
对比step2,step3执行完毕后,内存中保留的对象数量没有增加,这属于正常执行一个函数的情况。
在正常情况下,执行完一个函数之后,内存中保留的对象应该与执行前保持一致。
执行闭包因为没有引入新的引用,所以内存中保留的对象保持了一致。
那么问题来了,createComparisonFunction的活动对象究竟如何才能被销毁呢?
我们首先分析createComparisonFunction的活动对象存在的原因:闭包Closure(anonymous)的Function对象的Scope Chain引用了createComparisonFunction的活动对象,其目的是因为在闭包中需要访问propertyName这一属于createComparisonFunction活动对象的属性。
如果闭包Closure(anonymous)的Function对象被销毁,那么由于createComparisonFunction的活动对象不再被任何对象引用,它也会被销毁。
step4解除了compare对闭包的引用,使得闭包不再被任何对象引用,从而闭包被销毁。随之,createComparisonFunction的活动对象也因不再被任何对象引用而被销毁,这样就回收了这些对象所占用的内存。
使用闭包的问题在于其内存消耗通常比一般函数大,因为它需要保存额外的活动对象。因此,在不需要使用闭包时,应当将闭包解引用,以回收额外活动对象所占用的内存。
执行完step4之后内存中保留的对象包括:
1. Global Object
2. createComparisonFunction的Function对象 + Scope Chain
对比step1,step4在执行完毕后,没有额外的对象被保留在内存中,引用闭包所产生的额外对象都得到了回收。
更多关于JavaScript相关内容感兴趣的读者可查看本站专题:《javascript面向对象入门教程》、《JavaScript错误与调试技巧总结》、《JavaScript数据结构与算法技巧总结》、《JavaScript遍历算法与技巧总结》及《JavaScript数学运算用法总结》
希望本文所述对大家JavaScript程序设计有所帮助。