第 4 章 变量、作用域和内存问题
4.1 基本类型和引用类型的值
4.1.1 动态的属性
定义基本类型值和引用类型值的方式是类似的:创建一个变量并为该变量赋值。
var person = new Object();
person.name = "Nicholas";
alert(person.name); //"Nicholas"
但是,我们不能给基本类型的值添加属性,尽管这样做不会导致任何错误。比如:
var name = "Nicholas";
name.age = 27;
alert(name.age); //undefined
为字符串 name 定义了一个名为 age 的属性,为该属性赋值 27。但在下一
行访问这个属性时,发现该属性不见了。这说明只能给引用类型值动态地添加属性,以便将来使用。
4.1.2 复制变量值
从一个变量向另一个变量复制基本类型的值,会在变量对象上创建一个新值,然后把该值复制到为新变量分配的位置上。
var num1 = 5;
var num2 = num1;
4.1.3 传递参数
在向参数传递基本类型的值时,被传递的值会被复制给一个局部变量(即命名参数,或者用ECMAScript 的概念来说,就是 arguments 对象中的一个元素)。
function addTen(num) {
num += 10;
return num;
}
var count = 20;
var result = addTen(count);
alert(count); //20,没有变化
alert(result); //30
4.1.4 检测类型
typeof 操作符是确定一个变量是字符串、数值、布尔值,还是 undefined 的最佳工具
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
4.2 执行环境及作用域
var color = "blue";
function changeColor(){
if (color === "blue"){
color = "red";
} else {
color = "blue";
}
}
changeColor();
alert("Color is now " + color);
4.2.1 延长作用域链
虽然执行环境的类型总共只有两种——全局和局部(函数)
try-catch 语句的 catch 块;
with 语句。
这两个语句都会在作用域链的前端添加一个变量对象。对 with 语句来说,会将指定的对象添加到作用域链中。对 catch 语句来说,会创建一个新的变量对象,其中包含的是被抛出的错误对象的声明。
下面看一个例子。
function buildUrl() {
var qs = "?debug=true";
with(location){
var url = href + qs;
}
return url;
}
4.2.2 没有块级作用域
- 声明变量
使用 var 声明的变量会自动被添加到最接近的环境中。 在with 语句中,最接近的环境是函数环境
function add(num1, num2) {
var sum = num1 + num2;
return sum;
}
var result = add(10, 20); //30
alert(sum); //由于 sum 不是有效的变量,因此会导致错误
以上代码中的函数 add()定义了一个名为 sum 的局部变量,该变量包含加法操作的结果。虽然结果值从函数中返回了,但变量 sum 在函数外部是访问不到的。如果省略这个例子中的 var 关键字,那么当 add()执行完毕后,sum 也将可以访问到:
function add(num1, num2) {
sum = num1 + num2;
return sum;
}
var result = add(10, 20); //30
alert(sum); //30
- 查询标识符
当在某个环境中为了读取或写入而引用一个标识符时,必须通过搜索来确定该标识符实际代表什么
通过下面这个示例,可以理解查询标识符的过程:
var color = "blue";
function getColor(){
return color;
}
alert(getColor()); //"blue"
如果局部环境中存在着同名标识符,就不会使用位于父环境中的标识符,如下面的例子
所示:
var color = "blue";
function getColor(){
var color = "red";
return color;
}
alert(getColor()); //"red"
任何位于局部变量 color 的声明之后的代码,如果不使用 window.color 都无法访问全局 color变量。
变量查询也不是没有代价的。很明显,访问局部变量要比访问全局变量更快,因
为不用向上搜索作用域链。JavaScript 引擎在优化标识符查询方面做得不错,因此这
个差别在将来恐怕就可以忽略不计了。
4.3 垃圾收集
JavaScript 具有自动垃圾收集机制,也就是说,执行环境会负责管理代码执行过程中使用的内存。
函数中局部变量的正常生命周期,局部变量只在函数执行的过程中存在。
4.3.1 标记清除
JavaScript 中最常用的垃圾收集方式是标记清除(mark-and-sweep)。
4.3.2 引用计数
另一种不太常见的垃圾收集策略叫做引用计数(reference counting)。
4.3.3 性能问题
4.3.4 管理内存
4.4 小结
JavaScript 变量可以用来保存两种类型的值:基本类型值和引用类型值。基本类型的值源自以下 5种基本数据类型:Undefined、Null、Boolean、Number 和 String
。基本类型值和引用类型值具有以下特点:
基本类型值在内存中占据固定大小的空间,因此被保存在栈内存中;
从一个变量向另一个变量复制基本类型的值,会创建这个值的一个副本;
引用类型的值是对象,保存在堆内存中;
包含引用类型值的变量实际上包含的并不是对象本身,而是一个指向该对象的指针;
从一个变量向另一个变量复制引用类型的值,复制的其实是指针,因此两个变量最终都指向同一个对象;
确定一个值是哪种基本类型可以使用 typeof 操作符,而确定一个值是哪种引用类型可以使用instanceof 操作符。
所有变量(包括基本类型和引用类型)都存在于一个执行环境(也称为作用域)当中,这个执行环境决定了变量的生命周期,以及哪一部分代码可以访问其中的变量。
执行环境有全局执行环境(也称为全局环境)和函数执行环境之分;
每次进入一个新执行环境,都会创建一个用于搜索变量和函数的作用域链;
函数的局部环境不仅有权访问函数作用域中的变量,而且有权访问其包含(父)环境,乃至全局环境;
全局环境只能访问在全局环境中定义的变量和函数,而不能直接访问局部环境中的任何数据;
变量的执行环境有助于确定应该何时释放内存。