变量、作用域和内存问题

1. 基本类型和引用类型的值

  • 基本类型值指的是简单的数据段,而引用类型值指那些可能由多个值构成的对象。
  • 在很多语言中,字符串以对象的形式来表示,因此被认为是引用类型的。ECMAScript 放弃了这一传统。

1.1 动态的属性

//引用类型
var person = new Object();
person.name = "Nicholas";
alert(person.name); //"Nicholas"

//基本类型
var name = "Nicholas";
name.age = 27;
alert(name.age); //undefined

1.2 复制变量值

//基本类型
var num1 = 5;
var num2 = num1;
num1=3;
alert(num2); //"5"

//引用类型
var obj1 = new Object();
var obj2 = obj1;
obj1.name = "Nicholas";
alert(obj2.name); //"Nicholas"

1.3 传递参数

//证明引用类型也是值传递
function setName(obj) {
    obj.name = "Nicholas";
    obj = new Object();
    obj.name = "Greg";
}
var person = new Object();
setName(person);
alert(person.name); //"Nicholas"

1.4 检测类型

//检测基本类型
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

//检测引用类型
alert(person instanceof Object); // 变量 person 是 Object 吗?
alert(colors instanceof Array); // 变量 colors 是 Array 吗?
alert(pattern instanceof RegExp); // 变量 pattern 是 RegExp 吗?	
  • 根据规定,所有引用类型的值都是 Object 的实例。
    因此,在检测一个引用类型值和 Object 构造函数时, instanceof 操作符始终会返回 true。
    当然,如果使用 instanceof 操作符检测基本类型的值,则该操作符始终会返回 false,因为基本类型不是对象。

2. 执行环境及作用域

  • 作用域链;
  • 执行环境;
  • 全局环境;
  • 活动对象;

2.1 延长作用域链

当执行流进入下列任何一个语句时,作用域链就会得到加长:
1. try-catch 语句的 catch 块;
2. with 语句。

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

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

    return url;
}

2.2 没有块级作用域

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

  • 在编写 JavaScript 代码的过程中,不声明而直接初始化变量是一个常见的错误做法,因为这样可能会导致意外。
    我们建议在初始化变量之前,一定要先声明,这样就可以避免类似问题。
    在严格模式下,初始化未经声明的变量会导致错误。

2.2.2 查询标识符
  • 向上搜索作用域链;
  • 变量查询也不是没有代价的。
    很明显,访问局部变量要比访问全局变量更快,因为不用向上搜索作用域链。
    JavaScript 引擎在优化标识符查询方面做得不错,因此这个差别在将来恐怕就可以忽略不计了。

3 垃圾收集

3.1 标记清除

  • 垃圾收集器在运行的时候会给存储在内存中的所有变量都加上标记(当然,可以使用任何标记方式)。
    然后,它会去掉环境中的变量以及被环境中的变量引用的变量的标记。
    而在此之后再被加上标记的变量将被视为准备删除的变量。

  • 到 2008 年为止, IE、 Firefox、 Opera、 Chrome 和 Safari 的 JavaScript
    实现使用的都是标记清除式的垃圾收集策略(或类似的策略),只不过垃圾收集的时间间隔互有不同。

3.2 引用计数

//循环引用示例
function problem(){
    var objectA = new Object();
    var objectB = new Object();
    objectA.someOtherObject = objectB;
    objectB.anotherObject = objectA;
}
  • 内存泄漏;

3.3 性能问题

  • 随着 IE7 的发布,其 JavaScript 引擎的垃圾收集例程改变了工作方式:
    触发垃圾收集的变量分配、字面量和(或)数组元素的临界值被调整为动态修正。
    IE7 中的各项临界值在初始时与IE6相等。如果垃圾收集例程回收的内存分配量低于15%,
    则变量、字面量和(或)数组元素的临界值就会加倍。
    如果例程回收了 85%的内存分配量,则将各种临界值重置回默认值。
    这一看似简单的调整,极大地提升了 IE在运行包含大量 JavaScript 的页面时的性能。

  • 事实上,在有的浏览器中可以触发垃圾收集过程,但我们不建议读者这样做。
    在IE 中,调用 window.CollectGarbage()方法会立即执行垃圾收集。
    在 Opera 7 及更高版本中,调用 window.opera.collect()也会启动垃圾收集例程。

3.4 管理内存

function createPerson(name){
    var localPerson = new Object();
    localPerson.name = name;
    return localPerson;
}

var globalPerson = createPerson("Nicholas");

// 手工解除 globalPerson 的引用	
globalPerson = null;
  • 不过,解除一个值的引用并不意味着自动回收该值所占用的内存。
    解除引用的真正作用是让值脱离执行环境,以便垃圾收集器下次运行时将其回收。

4. 小结

  • 基本类型值和引用类型值

    1. 基本类型值在内存中占据固定大小的空间,因此被保存在栈内存中;
    2. 引用类型的值是对象,保存在堆内存中;
  • 执行环境

    1. 执行环境有全局执行环境和函数执行环境之分;
    2. 每次进入一个新执行环境,都会创建一个用于搜索变量和函数的作用域链;
    3. 变量的执行环境有助于确定应该何时释放内存;
  • 垃圾收集例程

    1. “标记清除”是目前主流的垃圾收集算法;
    2. “引用计数”容易导致“循环引用”,JavaScript 引擎目前都不再使用这种算法,
      但在 IE 中访问非原生 JavaScript 对象时,这种算法可能仍然会导致问题;
    3. 接触变量的引用有助于消除“循环应用”现象;
posted @ 2015-12-24 20:22  叫我霍啊啊啊  阅读(141)  评论(0编辑  收藏  举报