1. 基本类型和引用类型

和其他语言不太相同,JavaScript 在定义变量时必须确定这个值是基本类型还是引用类型,其中 NumberStringBooleanNullUndefined 都是基本类型。而 Object 则是引用类型。它们之间最大的区别在于基本类型是存储在内存中的值,而引用类型是存储在内存中的对象。

1.1 动态属性

无论是引用类型还是基本类型,其定义方法都是相同的,先创建一个变量然后为其赋值。不同的是,我们可以为引用类型的变量添加属性和方法,也可以修改它的属性和方法,就像下面这样

1
2
3
var person = new Object();
person.name = 'yuchi';
console.log(person.name);

这段代码打印输出字符串 yuchi 。我们一样可以为基本类型添加/修改属性与方法,这样不会导致任何错误,但事实上也没有任何意义。

1
2
3
var person = 'yuchi';
person.name = 'yuchi';
console.log(person.name);

上面的代码在控制台输出了 undefined

1.2 复制

当将一个基本类型的变量赋值给另一个变量时,实际开辟了新的内存空间来存储被复制的值,因而两个变量是独立的。
但当我们将一个引用类型的变量复制给另一个变量时,则两者指向了同一块内存空间,那么对应的,修改了其中一个的值,另一个也会发生变化。

1
2
3
4
5
6
7
8
9
10
11
var a = 1;
var b = a;

a ++;
console.log(a, b);

var obj_a = { name: 'xiaohong', age: 30 }
var obj_b = obj_a;

obj_a.age = 24;
console.log(obj_a, obj_b);

如上代码分别输出了 2 1{ name: 'xiaohong', age: 24 } { name: 'xiaohong', age: 24 }

1.3 传递参数

当向函数传递参数的时候,不管是引用类型还是基本类型,都会使用按值传递的方式,所谓按值传递,即把变量的值复制到一块新的内存空间然后赋值给新的变量(基本类型的赋值正是这个流程)。
当入参是基本类型时,则如同赋值一样,基本类型的变量被复制了一份传递给入参,两者相互独立,且入参被定义在了函数级作用域里,因而离开了函数作用域,入参将会被销毁。这些非常容易理解。
但是当参数是引用类型时,这些就会变的略微有些难以理解了,由于引用类型变量存储的是对象在内存中的地址,因而即便按值传递,入参与被传递的参数对象也会指向同一块内存。

1
2
3
4
5
6
7
var obj = { name: 'xiaoming', age: 18 };

function setName (o) { o.name = 'yuchi' }

setName(obj);

console.log(obj);

上述代码打印输出 { name: 'yuchi', age: 18 } 。当我们把代码进行一些修改后,上述的过程会更清晰一些:

1
2
3
4
5
6
7
8
var obj = { name: 'xiaoming', age: 18 };

function setName (o) {
o = new Object();
o.name = 'yuchi';
}

console.log(obj.name);

我们假设函数传参时,引用类型变量按引用传递,则在函数内部变量 o 与外部的 obj 指向同一块内存,那么当 o 被赋值为 new Object() 时,obj 也应该被修改为 new Objcet 。然而我们发现输出的结果是 xiaoming ,这恰巧说明了传递参数时,o 接收到的是变量的引用而非指向对象的内存。

2. 执行环境与作用域

执行环境中定义了变量或函数有权访问的数据,决定了各自的行为。每个执行环境都有一个与之关联的变量对象。
每个函数都有自己的执行环境,每当执行流进入一个函数,函数的执行环境会被推入环境栈中,而函数执行之后栈再将环境弹出。
代码在一个执行环境里执行时,会创建变量对象的作用域链,作用域链保证了执行环境有权访问的所有变量与函数的有序访问
当代码开始解析标识符(即变量/函数等)时,会沿着作用域链一层一层的搜索直到找到该标识符(如果找不到,则会抛出异常)。
其实说了这么多总结就一句话,使用一个变量时会沿着作用域一层一层的向外查找。

3. 垃圾回收

任何语言都需要合理的垃圾回收机制以维护程序的正常开销。下面介绍几种常用的垃圾回收机制。

3.1 标记清除

标记清除就像它的名字一样,当变量进入环境时,将其标记为进入环境,反之当变量离开环境时,将其标记为离开环境。垃圾收集器会为环境里所有的变量进行标记,然后它会去除掉当前环境中的变量以及它们所依赖的变量的标记,并将剩下的变量标记为准备删除的变量。最后完成内存清除的工作。

3.2 引用计数

引用计数会跟踪每个值被引用的次数,当声明了一个变量并将一个引用类型值赋给该变量时,这个值的引用次数增加 1,反之如果包含这个值引用的变量又取得了另一个值,则这个值的引用次数减少 1.当这个值减少至 0 时,则说明有办法再访问到这个值了,此时可以将它占用的空间回收。
引用计数存在的问题是,如果有两个引用类型的变量互相引用,如下:

1
2
3
4
5
var obj1 = new Object();
var obj2 = new Object();

obj1.one = obj2;
obj2.two = obj1;

则这两个变量的引用次数永远不会是 0,这种现象叫做循环引用

3.3 内存管理

为了更高效的管理应用的内存,我们应该手动释放不再使用的对象引用:

1
2
3
var Person = new Object();
...
Person = null;