变量、作用域和内存问题
1. 基本类型和引用类型的值
- 基本类型值指的是简单的数据段,而引用类型值指那些可能由多个值构成的对象。
- 在很多语言中,字符串以对象的形式来表示,因此被认为是引用类型的。ECMAScript 放弃了这一传统。
1.1 动态的属性
//引用类型
var person = new Object();
person.name = "Nicholas";
alert(person.name); //"Nicholas"
//基本类型
var name = "Nicholas";
name.age = 27;
alert(name.age); //undefined
1.2 复制变量值
//基本类型
var num1 = 5;
var num2 = num1;
num1=3;
alert(num2); //"5"
//引用类型
var obj1 = new Object();
var obj2 = obj1;
obj1.name = "Nicholas";
alert(obj2.name); //"Nicholas"
1.3 传递参数
//证明引用类型也是值传递
function setName(obj) {
obj.name = "Nicholas";
obj = new Object();
obj.name = "Greg";
}
var person = new Object();
setName(person);
alert(person.name); //"Nicholas"
1.4 检测类型
//检测基本类型
var s = "Nicholas";
var b = true;
var i = 22;
var u;
var n = null;
var o = new Object();
alert(typeof s); //string
alert(typeof i); //number
alert(typeof b); //boolean
alert(typeof u); //undefined
alert(typeof n); //object
alert(typeof o); //object
//检测引用类型
alert(person instanceof Object); // 变量 person 是 Object 吗?
alert(colors instanceof Array); // 变量 colors 是 Array 吗?
alert(pattern instanceof RegExp); // 变量 pattern 是 RegExp 吗?
- 根据规定,所有引用类型的值都是 Object 的实例。
因此,在检测一个引用类型值和 Object 构造函数时, instanceof 操作符始终会返回 true。
当然,如果使用 instanceof 操作符检测基本类型的值,则该操作符始终会返回 false,因为基本类型不是对象。
2. 执行环境及作用域
- 作用域链;
- 执行环境;
- 全局环境;
- 活动对象;
2.1 延长作用域链
当执行流进入下列任何一个语句时,作用域链就会得到加长:
1. try-catch 语句的 catch 块;
2. with 语句。
function buildUrl() {
var qs = "?debug=true";
with(location){
var url = href + qs;
}
return url;
}
2.2 没有块级作用域
2.2.1 声明变量
-
使用 var 声明的变量会自动被添加到最接近的环境中。
在函数内部,最接近的环境就是函数的局部环境;在 with 语句中,最接近的环境是函数环境。
如果初始化变量时没有使用 var 声明,该变量会自动被添加到全局环境。 -
在编写 JavaScript 代码的过程中,不声明而直接初始化变量是一个常见的错误做法,因为这样可能会导致意外。
我们建议在初始化变量之前,一定要先声明,这样就可以避免类似问题。
在严格模式下,初始化未经声明的变量会导致错误。
2.2.2 查询标识符
- 向上搜索作用域链;
- 变量查询也不是没有代价的。
很明显,访问局部变量要比访问全局变量更快,因为不用向上搜索作用域链。
JavaScript 引擎在优化标识符查询方面做得不错,因此这个差别在将来恐怕就可以忽略不计了。
3 垃圾收集
3.1 标记清除
-
垃圾收集器在运行的时候会给存储在内存中的所有变量都加上标记(当然,可以使用任何标记方式)。
然后,它会去掉环境中的变量以及被环境中的变量引用的变量的标记。
而在此之后再被加上标记的变量将被视为准备删除的变量。 -
到 2008 年为止, IE、 Firefox、 Opera、 Chrome 和 Safari 的 JavaScript
实现使用的都是标记清除式的垃圾收集策略(或类似的策略),只不过垃圾收集的时间间隔互有不同。
3.2 引用计数
//循环引用示例
function problem(){
var objectA = new Object();
var objectB = new Object();
objectA.someOtherObject = objectB;
objectB.anotherObject = objectA;
}
- 内存泄漏;
3.3 性能问题
-
随着 IE7 的发布,其 JavaScript 引擎的垃圾收集例程改变了工作方式:
触发垃圾收集的变量分配、字面量和(或)数组元素的临界值被调整为动态修正。
IE7 中的各项临界值在初始时与IE6相等。如果垃圾收集例程回收的内存分配量低于15%,
则变量、字面量和(或)数组元素的临界值就会加倍。
如果例程回收了 85%的内存分配量,则将各种临界值重置回默认值。
这一看似简单的调整,极大地提升了 IE在运行包含大量 JavaScript 的页面时的性能。 -
事实上,在有的浏览器中可以触发垃圾收集过程,但我们不建议读者这样做。
在IE 中,调用 window.CollectGarbage()方法会立即执行垃圾收集。
在 Opera 7 及更高版本中,调用 window.opera.collect()也会启动垃圾收集例程。
3.4 管理内存
function createPerson(name){
var localPerson = new Object();
localPerson.name = name;
return localPerson;
}
var globalPerson = createPerson("Nicholas");
// 手工解除 globalPerson 的引用
globalPerson = null;
- 不过,解除一个值的引用并不意味着自动回收该值所占用的内存。
解除引用的真正作用是让值脱离执行环境,以便垃圾收集器下次运行时将其回收。
4. 小结
-
基本类型值和引用类型值
- 基本类型值在内存中占据固定大小的空间,因此被保存在栈内存中;
- 引用类型的值是对象,保存在堆内存中;
-
执行环境
- 执行环境有全局执行环境和函数执行环境之分;
- 每次进入一个新执行环境,都会创建一个用于搜索变量和函数的作用域链;
- 变量的执行环境有助于确定应该何时释放内存;
-
垃圾收集例程
- “标记清除”是目前主流的垃圾收集算法;
- “引用计数”容易导致“循环引用”,JavaScript 引擎目前都不再使用这种算法,
但在 IE 中访问非原生 JavaScript 对象时,这种算法可能仍然会导致问题; - 接触变量的引用有助于消除“循环应用”现象;