JS变量和垃圾回收机制

基本类型和引用类型

js中的变量虽然不区分类型,可是实际上Ecmascript包含两种类型,基本类型和引用类型.java

基本类型有5种:Undefined,Null,Boolean,Number,String,基本类型是按值访问的,由于能够操做保存在变量中的实际的值。算法

引用类型的值是保存在内存中的对象。与其余语言不一样,JavaScript 不容许直接访问内存中的位置,也就是说不能直接操做对象的内存空间。在操做对象时,其实是在操做对象的引用(也称为句柄)而不是实际的对象.浏览器

注意:
字符串字面量是基本数据类型,这和其余的语言(例如java)有所区别.

js中函数参数的传递只有一种:值传递.请看下面的例子:bash

function setName (obj) {
    obj.name = 'hello';
}

var p = new Object();
setName(p);
console.log(p.name); // hello复制代码

以上代码中建立一个对象,并将其保存在了变量p中。而后,这个变量被传递到setName()函数中以后就被复制给了obj。在这个函数内部,objp引用的是同一个对象。换句话说,即便这个变量是按值传递的,obj也会按引用来访问同一个对象。因而,当在函数内部为obj添加name属性后,函数外部的p也将有所反映;由于p指向的对象在堆内存中只有一个,并且是全局对象。有不少开发人员错误地认为:在局部做用域中修改的对象会在全局做用域中反映出来,就说明参数是按引用传递的。为了证实对象是按值传递的,咱们再看一看下面这个通过修改的例子:函数

function setName (obj) {
    obj.name = 'hello';

    obj = new Object();
    obj.name = 'gray';
}

var p = new Object();
setName(p);
console.log(p.name); // hello复制代码

这个例子与前一个例子的惟一区别,就是在setName()函数中添加了两行代码:一行代码为obj从新定义了一个对象,另外一行代码为该对象定义了一个带有不一样值的name属性。在把p传递给setName()后,其name属性被设置为"hello"。而后,又将一个新对象赋给变量obj,同时将其name属性设置为"gray"。若是p是按引用传递的,那么p就会自动被修改成指向其name属性值为"gray"的新对象。可是,当接下来再访问person.name时,显示的值仍然是"hello"。这说明:即便在函数内部修改了参数的值,但原始的引用仍然保持未变。实际上,当在函数内部重写obj时,这个变量引用的就是一个局部对象了。而这个局部对象会在函数执行完毕后当即被销毁ui

能够把 ECMAScript 函数的参数想象成局部变量。

类型检测

typeof运算符在检测基本类型的时候很是有用,可以识别出:string,number,boolean,undefined,可是对null和对象进行此运算获得的都是'object'.此操做符对于检测对象来讲没有用(由于咱们一般是想知道某值是否是对象,咱们想知道它是什么的实例).spa

对于检测对象,咱们有instanceof运算符:指针

result = variable instanceof constructor复制代码

若是对基本数据类型进行instanceof运算,获得的结果将是false,由于基本类型不是对象.code

垃圾收集

垃圾收集机制其实很是简单,周期性地找出再也不继续使用的变量,释放其内存.标识无用变量的策略有如下的2种方式:对象

标记清除(mark-and-sweep)

绝大多数浏览器采用的垃圾收集机制.

当变量进入环境(例如,在函数中声明一个变量)时,就将这个变量标记为“进入环境”。从逻辑上讲,永远不能释放进入环境的变量所占用的内存,由于只要执行流进入相应的环境,就可能会用到它们。而当变量离开环境时,则将其标记为“离开环境”。

可使用任何方式来标记变量。好比,能够经过翻转某个特殊的位来记录一个变量什么时候进入环境,或者使用一个“进入环境的”变量列表及一个“离开环境的”变量列表来跟踪哪一个变量发生了变化。说到底,如何标记变量其实并不重要,关键在于采起什么策略.

垃圾收集器在运行的时候会给存储在内存中的全部变量都加上标记(固然,可使用任何标记方式)。而后,它会去掉环境中的变量以及被环境中的变量引用的变量的标记。而在此以后再被加上标记的变量将被视为准备删除的变量,缘由是环境中的变量已经没法访问到这些变量了。最后,垃圾收集器完成内存清除工做,销毁那些带标记的值并回收它们所占用的内存空间。

引用计数(reference counting)

跟踪记录每一个值被引用的次数。当声明了一个变量并将一个引用类型值赋给该变量时,则这个值的引用次数就是1。若是同一个值又被赋给另外一个变量,则该值的引用次数加1。相反,若是包含对这个值引用的变量又取得了另一个值,则这个值的引用次数减 1。当这个值的引用次数变成 0 时,则说明没有办法再访问这个值了,于是就能够将其占用的内存空间回收回来。这样,当垃圾收集器下次再运行时,它就会释放那些引用次数为零的值所占用的内存。

这种方式存在一个严重的问题循环引用.循环引用指的是对象 A 中包含一个指向对象 B 的指针,而对象 B 中也包含一个指向对象 A 的引用。

function problem() {
    var objectA = new Object();
    var objectB = new Object();
    objectA.someOtherObject = objectB;
    objectB.anotherObject = objectA;
}复制代码

在这个例子中, objectA 和 objectB 经过各自的属性相互引用;也就是说,这两个对象的引用次数都是2。在采用标记清除策略的实现中,因为函数执行以后,这两个对象都离开了做用域,所以这种相互引用不是个问题。但在采用引用计数策略的实现中,当函数执行完毕后,objectA 和 objectB 还将继续存在,由于它们的引用次数永远不会是0。假如这个函数被重复屡次调用,就会致使大量内存得不到回收。为此,Netscape 在 Navigator 4.0中放弃了引用计数方式,转而采用标记清除来实现其垃圾收集机制。但是,引用计数致使的麻烦并未就此终结。

IE 中有一部分对象并非原生JavaScript对象。例如,其BOM和DOM中的对象就是使用C++以COM(Component Object Model,组件对象模型)对象的形式实现的,而 COM 对象的垃圾收集机制采用的就是引用计数策略。所以,即便IE的JavaScript引擎是使用标记清除策略来实现的,但JavaScript访问的COM对象依然是基于引用计数策略的。换句话说,只要在 IE 中涉及 COM 对象,就会存在循环引用的问题。为了解决上述问题,IE9 把 BOM 和 DOM 对象都转换成了真正的 JavaScript 对象。这样,就避免了两种垃圾收集算法并存致使的问题,也消除了常见的内存泄漏现象。

相关文章
相关标签/搜索