一、执行环境及作用域
概念:
执行环境定义了变量或函数有权访问的其他数据,决定了它们各自的行为。
每个执行环境都有一个 与之关联的变量对象
,环境中定义的所有变量和函数都保存在这个对象中。
每个函数都有自己的
执行环境
。当执行流进入一个函数时,函数的环境就会被推入一个环境栈中。 而在函数执行之后,栈将其环境弹出,把控制权返回给之前的执行环境。
当代码在一个环境中执行时,会创建变量对象的一个
作用域链
。作用域链的用途,是有序访问执行环境所能够访问的所有变量和函数。作用域链的前端,始终都是当前执行函数所在环境的变量对象,也叫做活动对象。
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()函数时,会创建一个预先包含全局变量对象的作用域链,这个作用域链被保存在内部的[[Scope]]属性中。
- 当调用 compare()函数时,会为函数创建一个执行环境,然后通过复制函数的[[Scope]]属性中的对象构建起执行环境的作用域链。
- 创建一个活动对象(在此作为变量对象使用)并被推入执行环境作用域链的前端。
对于这个例子中 compare()
函数的执行环境而言,其作用域链中包含两个变量对象:本地活动对象和全局变量对象。显然,作用域链本质上是一个指向变量对象的指针列表,它只引用但不实际包含变量对象。
1.1 延长作用域链
执行环境的类型总共只有两种——全局和局部(函数),但还是有其他办法来延长作用域链。
延长作用域链的原理:
有些语句可以在作用域链的前端临时增加一个变量对象,该变量对象会在代码执行后被移除
- try-catch 语句的 catch 块;
- with 语句。
这两个语句都会在作用域链的前端添加一个变量对象。
对 with
语句来说,会将指定的对象添加到 作用域链中。
对
catch
语句来说,会创建一个新的变量对象,其中包含的是被抛出的错误对象的声明。
1.2 没有块级作用域
对于有块级作用域的语言来说,
for
语句初始化变量的表达式所定义的变量,只会存在于循环的环 境之中。而对于 JavaScript
来说,由
for
语句创建的变量
i
即使在
for
循环执行结束后,也依旧会存在 于循环外部的执行环境中。
二、垃圾收集
JavaScript
具有自动垃圾收集机制,执行环境会负责管理代码执行过程中使用的内存。
垃圾收集机制的原理:找出不再继续使用的变量,释放其占用的内存。为此,垃圾收集器会按照固定的时间间隔,周期性地执行这一操作。
两个策略:标记清除,引用计数
2.1 标记清除(常用)
标记:当变量进入环境(例如,在函数中声明一个变量)时,就将这个变量标记为“进入环境”。而当变量离开环境时,则将其标记为“离开环境”。
垃圾收集器在运行的时候会给存储在内存中的所有变量都加上标记(可以使用任何标记方式)。然后去掉环境中的变量以及被环境中的变量引用的变量的标记。之后还有标记的变量将被视为准备删除的变量,原因是环境中的变量已经无法访问到这些变量了。最后,垃圾收集器完成内存清除
工作,销毁那些带标记的值并回收它们所占用的内存空间
2.2 引用计数(不常用)
引用计数:跟踪记录每个值被引用的次数。
当声明了一个变量并将一个引用类型值赋给该变量时,则这个值的引用次数就是 1
。如果同一个值又被赋给另一个变量,则该值的引用次数加 1
。相反,如果包含对这个值引用的变量又取得了另外一个值,则这个值的引用次数减 1
。当这个值的引用次数变成
0
时,则说明没有办法再访问这个值了,因而就可以将其占用的内存空间回收回来。这样,当垃圾收集器下次再运行时,它就会释放那些引用次数为零的值所占用的内存。
这个方法不常用的原因是:引用循环导致的问题。
引用循环:对象 A 中包含一个指向对象 B 的指针,而对象 B 中也包含一个指向对象 A 的引用。
function problem(){
var objectA = new Object();
var objectB = new Object();
objectA.someOtherObject = objectB;
objectB.anotherObject = objectA;
}
在这个例子中,
objectA
和
objectB
通过各自的属性相互引用;也就是说,这两个对象的引用次数都是 2,对象得不到回收
为避免循环引用问题,最好是在不使用它们的时候手工断开对象与其引用之间的连接。
objectA.someOtherObject = null;
objectB.anotherObject = null;
总结:
执行环境与作用域
所有变量(包括基本类型和引用类型)都存在于一个执行环境(也称为作用域)当中,这个执行环境决定了变量的生命周期,以及哪一部分代码可以访问其中的变量。以下是关于执行环境的几点总结:
- 执行环境有全局执行环境(也称为全局环境)和函数执行环境之分;
- 每次进入一个新执行环境,都会创建一个用于搜索变量和函数的作用域链;
- 函数的局部环境不仅有权访问函数作用域中的变量,而且有权访问其包含(父)环境,乃至全局环境;
- 全局环境只能访问在全局环境中定义的变量和函数,而不能直接访问局部环境中的任何数据;
- 变量的执行环境有助于确定应该何时释放内存。
垃圾回收机制
JavaScript
是一门具有自动垃圾收集机制的编程语言,开发人员不必关心内存分配和回收问题。可以对 JavaScript
的垃圾收集例程作如下总结。
- 离开作用域的值将被自动标记为可以回收,因此将在垃圾收集期间被删除。
- “标记清除”是目前主流的垃圾收集算法,这种算法的思想是给当前不使用的值加上标记,然后再回收其内存。
- 另一种垃圾收集算法是“引用计数”,这种算法的思想是跟踪记录所有值被引用的次数。JavaScript引擎目前都不再使用这种算法;但在 IE 中访问非原生 JavaScript 对象(如 DOM 元素)时,这种算法仍然可能会导致问题。
- 当代码中存在循环引用现象时,“引用计数”算法就会导致问题。
- 解除变量的引用不仅有助于消除循环引用现象,而且对垃圾收集也有好处。为了确保有效地回收内存,应该及时解除不再使用的全局对象、全局对象属性以及循环引用变量的引用。