重读《javascript高级程序设计》第三版笔记 变量、作用于和内存问题

变量、作用域和内存问题

该章节主要讲解了以下三个重点内容:

  • 理解基本类型和引用类型的值
  • 理解执行环境
  • 理解垃圾回收

基本类型和引用类型的值

首先JavaScript中的变量分为基本类型和引用类型。

基本类型就是保存在栈内存中的简单数据段,而引用类型指的是那些保存在堆内存中的对象。

基本类型

基本类型有Undefined、Null、Boolean、Number 和String。这些类型在内存中分别占有固定大小的空间,他们的值保存在栈空间,我们通过按值来访问的。

引用类型

引用类型的值大小不固定,因此不能把它们放在栈内存中,但是内存地址的大小是固定的,可以把内存地址存在栈内存中,在堆中为这个地址分配空间。那么我们如果访问一个引用类型的变量,流程就是先从栈内存中读取变量的地址,通过这个地址去找到存储在堆内存中的值。对于这种方式,叫做引用访问

image

执行环境

每个函数都有自己的执行环境。当执行流进入一个函数时,函数的环境就会被推入一个环境栈中。 而在函数执行之后,栈将其环境弹出,把控制权返回给之前的执行环境。ECMAScript 程序中的执行流 正是由这个方便的机制控制着。 5 当代码在一个环境中执行时,会创建变量对象的一个作用域链(scope chain)。作用域链的用途,是 保证对执行环境有权访问的所有变量和函数的有序访问。作用域链的前端,始终都是当前执行的代码所 在环境的变量对象。如果这个环境是函数,则将其活动对象(activation object)作为变量对象。活动对 6 象在最开始时只包含一个变量,即 arguments 对象(这个对象在全局环境中是不存在的)。作用域链中 的下一个变量对象来自包含(外部)环境,而再下一个变量对象则来自下一个包含环境。这样,一直延 续到全局执行环境;全局执行环境的变量对象始终都是作用域链中的最后一个对象。

var color = "blue";
    function changeColor(){
        var anotherColor = "red";
        function swapColors(){
            var tempColor = anotherColor;
            anotherColor = color;
            color = tempColor;
            // 这里可以访问color、anotherColor和tempColor 
        }
        // 这里可以访问color和anotherColor,但不能访问tempColor
        swapColors();
    }
// 这里只能访问color changeColor();

简单的理解作用域链带来的效果,即内部环境可以通过作用域链访问所有的外部环境,但 外部环境不能访问内部环境中的任何变量和函数

延长作用域链

两个例子:

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

二者都会把相关的对象变量,添加到作用域链的前端,也就是在当前执行环境下找到这个对象变量的值,而不需要往上搜索去查找:

function buildUrl() {
    var qs = "?debug=true";
    with(location){
        var url = href + qs;
    }
    return url; 
}

无块级作用域

两个典型的语句if以及for

if (true) {
    var color = "blue";
}
alert(color);    //"blue"
for (var i=0; i < 10; i++){
    doSomething(i);
}
alert(i);      //10

blue和i,都被if和for语句添加到了当前的执行环境中,注意,这里的当前执行环境不一定是全局环境

垃圾回收

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

这种垃圾收集机制的原理其实很简单:找出那些不再继续使用的变量,然后释放其占用的内存。为此,垃圾收集器会按照固定的时间间隔(或代码执行中预定的收集时间), 周期性地执行这一操作。

标记清除

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

 function add() { var num = 1; //num进入环境 num += 1 return num //num离开环境,被回收 }

在函数add的环境中,声明了num变量,num会被当前环境标记为“进入环境”,而在return num,会被环境重新标记为“离开环境”

全局变量的生命周期直至浏览器卸载页面才会结束

引用计数

这是最简单的垃圾收集算法。此算法把“对象是否不再需要”简化定义为“对象有没有其他对象引用它”。如果没有引用指向该对象(零引用),对象将被垃圾回收机制回收。

function fn () { var a = {} var b = {} a.type = b b.type = a }

fn()

虽然在fn()执行结束后,a、b都被标记”离开环境”,但由于在引用计数策略下,a、b的引用次数都不为0,因此都不能被垃圾回收器回收内容。但可以手动解除引用:

a.type = null b.type = null