JavaScript 变量、作用域和内存问题

基本类型和引用类型的值


基本类型值指的是简单的数据段,而引用类型值指的是那些可能由多个值构成的对象。
javascript不允许直接访问内存中的位置,也就是说不能直接操作对象的内存空间。

在很多语言中,字符串以对象的形式表示,因此被认为是引用类型的。ECAMScript放弃了这一传统。

动态的属性

对于引用类型的值,我们可以添加、改变和删除其属性和方法。

复制变量值

从一个变量向另一个变量复制基本类型的值,会在变量对象上创建一个新值,然后把该值复制到为新变量分配的位置上。

var num1  =5;
var num2 =num1;

num1和num2中的5是完全独立的,num2中的5只是num1中5的一个副本。

从一个变量向另一个变量复制引用类型的值,同样也会将存储在变量对象中的值复制一份放到新变量分配的空间中。不同的是,这个值的副本实际上是一个指针。

var obj1 = new Object();
var obj2 = obj1;
obj1.name = 'asdf';
alert(obj2.name); //asdf

传递参数

ECMAScript中所有函数的参数都是按值传递的。

function addTen (num) {
    num +=10;
    return num;
}
var count = 20;
var result = addTen(count);
alert(count); //20,没有变化
function setName(obj) {
    obj.name = 'asdf';
}
var person = new Object();
setName(person);
alert(person.name); //asdf

很多人错误的认为:在局部作用域中修改的对象会在全局作用域中反映出来,就说明参数是按引用传递的。看下面的例子:

function setName(obj) {
    obj.name = 'asdf';
    obj = new Object();
    obj.name = 'qwer';
}

var person = new Object();
setName(person);
alert(person.name); //asdf

检测类型

typeof操作符用来检测变量是不是基本类型。
instanceof操作符用来检测引用类型。

alert(person instanceof Objcet);

使用typeof操作符检测函数是,会返回function。在Safari 5及之前版本和Chrome7之前版本中使用typeof检测正则表达式时,由于规范的原因,这个操作符也会返回function。ECMA-262规定任何内部实现[[call]]方法的对象都应该在应用typeof操作符时返回function。在IE和Firefox中,对正则表达式应用typeof时会返回Object。

执行环境及作用域


执行环境(execution context)定义了变量或函数有权访问的其他数据,决定了它们各自的行为。每个执行环境都有一个与之关联的变量对象(variable object),环境中定义的所有变量和函数都保存在这个对象中。虽然我们编写的代码访问不到,但解析器在处理数据时会在后台使用它。

全局执行环境是最外围的一个执行环境。根据ECMAScript实现所在宿主环境不同,表示执行环境的对象也不一样。在Web浏览器中,全局执行环境被认为是window对象,因此所有全局变量和函数都是作为window对象的属性和方法创建的。某个执行环境中的所有代码执行完毕后,该环境被销毁,保存在其中的所有变量和函数定义也随之销毁(全局执行环境直到应用程序退出--例如关闭网页或浏览器--时才会被销毁)。

每个函数都有自己的执行环境。

当代码在一个环境中执行时,会创建变量对象的一个作用域链(scope chain)。作用域链的用途是保证对执行环境有权访问的所有变量和函数的有序访问。作用域链的前端,始终都是当前执行的代码所在环境的变量对象。如果这个环境是函数,则将其活动对象(activation object)作为变量对象。活动对象在最开始时只包含一个变量,即arguments对象(这个对象在全局环境中是不存在的)。全局执行环境的变量对象始终都是作用域链中的最后一个对象。

延长作用域链

虽然执行环境只有两种:全局和局部(函数),但还有其他办法来延长作用域链。因为有些语句可以在作用域链的前端临时增加一个变量对象,该变量对象会在代码执行后被移除。具体来说,就是当执行流进入下列任何一个语句时,作用域链就会得到加长:

  • try-catch语句的catch块
  • with语句

对于with语句来说,会将指定的对象添加到作用域链中。对catch语句来说,会创建一个新的变量对象,其中包含的是被抛出的错误对象的声明。

function buildUrl () {
    var qs = "?debug=true";

    with (location) {
        var url = href + qs;
    }
    return url;
}

在IE8及之前版本的JavaScript实现中,存在一个与标准不一致的地方,即catch语句中捕获的错误对象会被添加到执行环境的变量对象,而不是catch语句的变量对象中。换句话,即使是在catch块的外部也可以访问到错误对象。IE9修复了这个问题。

没有块级作用域

声明变量
使用var声明的变量会自动被添加到最接近的环境中。在函数内部,最接近的环境就是函数的局部环境。如果初始化变量时没有使用var声明,该变量会自动添加到全局环境。

在严格模式下,初始化未经声明的变量会导致错误。

查询标识符

垃圾收集


JavaScript具有自动垃圾回收机制,执行环境会负责管理代码执行过程中使用的内存。
垃圾收集器必须跟踪哪个变量有用哪个变量没用,对于不在有用的变量打上标记,以备将来收回其占用的内存。用于标识无用变量的策略可能会因实现而异,但具体到浏览器中的实现,则通常有两个策略。

标记清除

JavaScript中常用的垃圾收集方式是标记清除(mark-and-sweep)。

推荐阅读更多精彩内容