【读书笔记】JavaScript 变量、作用域和内存问题

314 阅读7分钟

JavaScript 变量松散类型的本质,决定了它只是在特定时间用于保存特定值的一个名字而已。由于不存在定义某个变量必须要保存何种数据类型值的规则,变量的值及其数据类型可以在脚本的生命周期内改变。

4.1 基本类型和引用类型的值

ECMAScript 变量可能包含两种不同数据类型的值:基本类型值和引用类型值。基本类型值指的是 简单的数据段,(Undefined 、 Null 、 Boolean 、 Number 和String),而引用类型值指那些可能由多个值构成的对象。

  • 4.1.1 动态的属性

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

var person = new Object();
person.name = "Nicholas";
alert(person.name); //"Nicholas"
  • 4.1.2 复制变量值

    • 从一个变量向另一个变量复制基本类型的值,会在变量对象上创建一个新值,然后把该值复制 到为新变量分配的位置上。
    var num1 = 5;
    var num2 = num1;
    
    • 当从一个变量向另一个变量复制引用类型的值时,同样也会将存储在变量对象中的值复制一份放到 为新变量分配的空间中。不同的是,这个值的副本实际上是一个指针,而这个指针指向存储在堆中的一 个对象。
    var obj1 = new Object();
    var obj2 = obj1;
    obj1.name = "Nicholas";
    alert(obj2.name); //"Nicholas"
    
  • 4.1.3 传递参数

ECMAScript 中所有函数的参数都是按值传递的。也就是说,把函数外部的值复制给函数内部的参数,就和把值从一个变量复制到另一个变量一样。基本类型值的传递如同基本类型变量的复制一样,而 引用类型值的传递,则如同引用类型变量的复制一样。

  • 4.1.4 检测类型
    • typeof 操作符是确定一个变量是字符串、数值、布尔值,还是 undefined 的最佳工具
    • instanceof 操作符可以确定一个引用类型值和 Object 构造函数

4.2 执行环境及作用域

执行环境:定义了变量或函数有权访问的其他数据,决定了它们各自的行为。每个执行环境都有一个 与之关联的变量对象。
当代码在一个环境中执行时,会创建变量对象的一个作用域链。作用域链的用途,是 保证对执行环境有权访问的所有变量和函数的有序访问。
如果这个环境是函数,则将其活动对象(activation object)作为变量对象。

var color = "blue";
function changeColor(){
    if (color === "blue"){
    color = "red";
    } else {
    color = "blue";
    }
}
changeColor();
alert("Color is now " + color);
//在这个简单的例子中,函数 changeColor() 的作用域链包含两个对象:它自己的变量对象(其中
定义着 arguments 对象)和全局环境的变量对象。可以在函数内部访问变量 color ,就是因为可以在
这个作用域链中找到它。
  • 4.2.1 延长作用域链
    • try-catch 语句的 catch 块;

    • with 语句。

  • 4.2.2 没有块级作用域
      1. 声明变量

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

      1. 查询标识符

      当在某个环境中为了读取或写入而引用一个标识符时,必须通过搜索来确定该标识符实际代表什 么。搜索过程从作用域链的前端开始,向上逐级查询与给定名字匹配的标识符。如果在局部环境中找到了该标识符,搜索过程停止,变量就绪。如果在局部环境中没有找到该变量名,则继续沿作用 域链向上搜索。搜索过程将一直追溯到全局环境的变量对象。如果在全局环境中也没有找到这个标识符,则意味着该变量尚未声明。

4.3 垃圾收集

JavaScript 具有自动垃圾收集机制,也就是说,执行环境会负责管理代码执行过程中使用的内存。

  • 4.3.1 标记清除 JavaScript 中最常用的垃圾收集方式是标记清除。当变量进入环境(例如,在函 数中声明一个变量)时,就将这个变量标记为“进入环境”。从逻辑上讲,永远不能释放进入环境的变量所占用的内存,因为只要执行流进入相应的环境,就可能会用到它们。而当变量离开环境时,则将其 标记为“离开环境”。
  • 4.3.2 引用计数 引用计数的含义是跟踪记录每个值被引用的次数。当声明了一个变量并将一个引用类型值赋给该变量时,则这个值的引用次数就是 1。 如果同一个值又被赋给另一个变量,则该值的引用次数加 1。相反,如果包含对这个值引用的变量又取 得了另外一个值,则这个值的引用次数减 1。当这个值的引用次数变成 0 时,则说明没有办法再访问这 个值了,因而就可以将其占用的内存空间回收回来。
  • 4.3.3 性能问题
  • 4.3.4 管理内存

4.4 小结

JavaScript 变量可以用来保存两种类型的值:基本类型值和引用类型值。基本类型的值源自以下 5 种基本数据类型: Undefined 、 Null 、 Boolean 、 Number 和 String 。基本类型值和引用类型值具 有以下特点:

基本类型值在内存中占据固定大小的空间,因此被保存在栈内存中;

从一个变量向另一个变量复制基本类型的值,会创建这个值的一个副本;

引用类型的值是对象,保存在堆内存中;

包含引用类型值的变量实际上包含的并不是对象本身,而是一个指向该对象的指针;

从一个变量向另一个变量复制引用类型的值,复制的其实是指针,因此两个变量最终都指向同 一个对象;

确定一个值是哪种基本类型可以使用 typeof 操作符,而确定一个值是哪种引用类型可以使用 instanceof 操作符。

所有变量(包括基本类型和引用类型)都存在于一个执行环境(也称为作用域)当中,这个执 行环境决定了变量的生命周期,以及哪一部分代码可以访问其中的变量。以下是关于执行环境的几 点总结:

执行环境有全局执行环境(也称为全局环境)和函数执行环境之分;

每次进入一个新执行环境,都会创建一个用于搜索变量和函数的作用域链;

函数的局部环境不仅有权访问函数作用域中的变量,而且有权访问其包含(父)环境,乃至全 局环境;

全局环境只能访问在全局环境中定义的变量和函数,而不能直接访问局部环境中的任何数据; 变量的执行环境有助于确定应该何时释放内存。

JavaScript 是一门具有自动垃圾收集机制的编程语言,开发人员不必关心内存分配和回收问题。可 以对 JavaScript 的垃圾收集例程作如下总结。

离开作用域的值将被自动标记为可以回收,因此将在垃圾收集期间被删除。

“标记清除”是目前主流的垃圾收集算法,这种算法的思想是给当前不使用的值加上标记,然 后再回收其内存。

另一种垃圾收集算法是“引用计数”,这种算法的思想是跟踪记录所有值被引用的次数。JavaScript 引擎目前都不再使用这种算法;但在 IE 中访问非原生 JavaScript 对象(如 DOM 元素)时,这种 算法仍然可能会导致问题。

当代码中存在循环引用现象时,“引用计数”算法就会导致问题。

解除变量的引用不仅有助于消除循环引用现象,而且对垃圾收集也有好处。为了确保有效地回 收内存,应该及时解除不再使用的全局对象、全局对象属性以及循环引用变量的引用。