《Javascript高级程序设计(第二版)》第四章 变量、作用域和内存问题

  1. 基本类型和引用类型的值
  • 基本类型值是指那些保存在栈内存中的简单数据,即这种值完全保存在内存中的一个位置。
  • 引用类型值是指那些保存在堆内存中的对象,意思是变量中实际保存的是一个指针,这个指针指向内存中的另一个位置,该位置保存对象。

  1. 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
  1. 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);

 

  1. 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时,这个变量的引用就是一个局部变量了。而这个局部变量会在函数执行完毕后立即销毁。

  1. 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具有自动垃圾收集机制。

posted @ 2015-06-19 15:48  刘牛牛  阅读(79)  评论(0编辑  收藏  举报