《Javascript高级程序设计(第二版)》第四章 变量、作用域和内存问题
- 基本类型和引用类型的值
- 基本类型值是指那些保存在栈内存中的简单数据,即这种值完全保存在内存中的一个位置。
- 引用类型值是指那些保存在堆内存中的对象,意思是变量中实际保存的是一个指针,这个指针指向内存中的另一个位置,该位置保存对象。
- 1 动态属性
- 对于引用类型的值,我们可以为其添加属性和方法,也可以改变和删除其属性和方法。如果对象不被销毁或这个属性不被删除,则这个属性将一直存在。
1 var person = new Object(); 2 person.name = "liuyan"; 3 alert(person.name); //liuyan
- 但是,我们不能给基本类型的值添加属性,尽管这样做不会导致任何错误。比如:
1 var name = "liuyan"; 2 name.age = 22; 3 alert(name.age); //undefined
- 2 复制变量值
- 如果从一个变量向另一个变量复制基本类型的值,会在栈中创建一个新值,然后把新值复制到为新变量分配的位置上。比如:
1 var num1 = 5; 2 var num2 = num1;
- 如果从一个变量向另一个变量复制引用类型的值,同样会将存储在栈中的值复制一份放到为新变量分配的空间中。不同的是,这个值的副本实际上是一个指针,而这个指针指向存储在堆中的一个对象。复制操作结束后,两个变量实际上将引用同一个对象。因此,改变其中的一个变量,就会影响另一个变量。比如:
1 var obj1 = new Object(); 2 var obj2 = obj1; 3 obj1.name = "liuyan"; 4 alert(obj2.name);
- 3 传递参数
- 访问变量有按值和按引用两种方式,而参数只能按值传递。
- 为了证明对象是按值传递的,我们来看看下面的例子:
1 function setName(obj){ 2 obj.name = "liuyan"; 3 obj = new Object(); 4 obj.name = "liumin"; 5 } 6 var person = new Object(); 7 setName(person); 8 alert(person.name); //liuyan
这说明即使在函数内部修改了函数的值,但原始的引用仍保持不变。实际上,在函数内部重写object时,这个变量的引用就是一个局部变量了。而这个局部变量会在函数执行完毕后立即销毁。
- 4 检测类型
- typeof操作符是确定一个变量是字符串、数值、布尔值,还是undefined的最佳工具。如果变量的值是一个对象或null,则在typeof操作符下会返回“object”。
1 var a = "liuyan"; 2 var b = true; 3 var c = 22; 4 var d = null; 5 var e; 6 var o = new Object(); 7 8 alert(typeof a); 9 alert(typeof b); 10 alert(typeof c); 11 alert(typeof d); 12 alert(typeof e); 13 alert(typeof o);
- 虽然在检测基本数据类型时typeof是非常得力的助手,但在检测引用类型的值时,这个操作符的用处不大。通常我们并不想知道某个值是对象,但是想知道他是什么类型的对象。为此,ECMAScript提供了instanceof操作符,比如:
1 var a = "liuyan"; 2 var b = true; 3 var c = 22; 4 var d = null; 5 var e; 6 var o = new Object(); 7 8 alert(a instanceof Array); //false 9 alert(b instanceof Boolean); //false 10 alert(c instanceof Number); //false 11 alert(d instanceof Object); //false 12 alert(d instanceof Object); //false 13 alert(o instanceof Object); //true
根据规定,所有引用类型的值都是object的实例。因此,在检测一个引用类型值和object构造函数时,instanceof操作符始终会返回true。如果使用instanceof操作符来检测基本数据类型的话,那始终都会返回false,因为基本类型不是对象。
注意:使用typeof操作符检测函数时,该操作符会返回“function”。在safari和chrome中使用typeof来检测正则表达式时,这个操作符会错误地返回“function”。
2. 执行环境及作用域
- 全局执行环境是最外围的一个执行环境。根据ECMAScript实现所在的宿主环境不同,表示执行环境的对象也不一样。在web浏览器中,全局执行环境被认为是“window”对象,因此所有全局变量和函数都是作为window对象的属性和方法创建的。
- 某个执行环境中的所有代码执行完毕后,该环境被销毁,保存在其中的变量和函数定义也随之销毁(全局执行环境直到应用程序退出——例如关闭网页或浏览器——时才会被销毁)。
- 作用域链的用途:是保证对执行环境有权访问的的所有变量和函数的有序访问。
- 标识符解析是沿着作用域链一级一级地搜索标识符的过程。搜索过程始终从作用域链的前端开始,然后逐级地向后回溯,直到找到标识符为止(如果找不到标识符,容易出错)。
-
1 var color = "blue"; 2 function changeColor(){ 3 var anotherColor = "red"; 4 function swapColors(){ 5 var tempColor = anotherColor; 6 anotherColor = color; 7 color = tempColor; 8 } 9 swapColors(); 10 } 11 changeColor(); 12 alert("Color is now "+color);
我们可以用一个图来表示这个执行环境:
其中,内部环境可以通过作用域链访问所有的外部环境,但外部环境不能访问内部环境中的任何变量和函数。另外,函数参数也被当做是变量来对待,因此其访问规则与执行环境中的其他变量相同。
2.1 延长作用域链
- 虽然执行环境的类型只有两种——全局和局部(函数),但还是有其他的方法来延长作用域链的。比如说可以在作用域链的前端临时增加一个变量对象,该变量对象会在代码执行后被移除。那在什么情况下会出现这种想象呢:
- try-catch语句的catch语句块
- with语句
-
1 function buildUrl(){ 2 var qs = "?debug=true"; 3 4 with(location){ 5 var url = href + qs; 6 } 7 return url; 8 } 9 10 var result = buildUrl(); 11 alert(result);
with语句接收的是location对象,因此其变量对象中就包含了location对象的所有属性和方法,而这个变量对象被添加到了作用域的前端。当with语句引用变量href时(实际引用的是location.href),可以在当前执行环境的变量对象中找到。
2.2 没有块级作用域
1 if(true){ 2 var color = "blue"; 3 } 4 alert(color); //blue
这里在if语句中定义了一个color变量。如果是在C、C++或java中,由花括号封闭的代码块都有自己的作用域(用ECMAScript的话来讲,就是他们自己的执行环境),所以color会在if语句执行后被销毁。但在javascript中是没有块级作用域的,if语句中的变量声明会将变量添加到当前的执行环境(在这里是全局环境)中。
特别是在使用for语句的时候要特别注意这一点,例如:
1 for(var i=0; i < 10; i++){ 2 //doSomething(i); 3 } 4 alert(i); //10
对于没有块级作用域的语言来说,for语句初始化变量的表达式所定义的变量,只会存在于循环的环境之中。而对于javascript来说,由for语句创建的变量i即使在for循环结束后,也仍旧会存在于循环外部的执行环境中。
- 声明变量
如果变量在未经声明的情况下被初始化,那么该变量会自动添加到全局环境。
声明了函数体内局部变量的例子如下:
1 function add(num1,num2){ 2 var sum = num1 + num2; 3 return sum; 4 } 5 var result = add(10,20); //30 6 alert(sum); //"error":sum is not defined
由于sum在函数外部是访问不到的,所以alert(sum)时报错。
如果省略这个var关键字,那么当add()执行完毕后,sum也将可以访问到:
1 function add(num1,num2){ 2 sum = num1 + num2; 3 return sum; 4 } 5 var result = add(10,20); //30 6 alert(sum); //30
在编写javascript代码的过程中,不声明而直接初始化变量是一个常见的错误做法,因为这样可能会导致意外。我们的建议是在初始化之前,一定要先声明,这样可以避免类似的问题。
- 查询标识符
采用向上搜索,从局部变量开始,如果局部变量中找到了目标变量则停止搜索,反之继续向上搜索全局变量,知道找到目标变量为止。
- 垃圾收集
javascript具有自动垃圾收集机制。