原始值与引用值

ECMAScript可以包含两种不同类型的数据:原始值和引用值。

  • 原始值:最简单的数据。
  • 引用值(reference value):多个值构成的对象。

当把一个值赋值给变量的时,Javascript引擎需要确定这个值是原始值还是引用值。

原始值

上个文章讨论了六个原始值:Undefined、Null、Boolean、Number、String和Symbol。

保存原始值的变量是按值访问的,我们操作的就是存储在变量中的值。

引用值

引用值是保存在内存中的对象,JavaScript不允许直接访问内存,因此不能直接操作对象所在的内存空间。

在操作对象时,实际上操作的是对该对象的引用而非实际的对象本身。保存引用值的对象是按引用访问的。

注意:在其它语言中,字符串被当作对象引用。JavaScript打破了这个规则。

动态属性

对于引用值,可以随时添加、修改和删除其属性和方法:

1
2
3
let person = new Object();
person.name = "Nicholas";
console.log(person.name); //"Nicholas"

这里,创建了一个对象并将其保存在变量person中。

然后给这个对象添加了一个name属性并赋值。

复制值

对原始值变量的复制是创建副本,得到两个独立的变量。

对引用值变量的复制则是让它们指向同一对象。也就是说,引用值功能上相等于指向对象的指针。

传递参数

和类C语言一样,参数的传递是值传递。

对原始值来言,是传递一个值,并在函数内部创建局部变量作为副本。

对引用值来言,传递指向对象的引用,函数内部创建一个对对象的引用,可以修改外部变量的值。

确定类型

可以使用typeof运算符确定类型,如果值是null或对象返回”object”。

如果我们想知道这个对象的类型,可以使用instanceOf操作符。

1
result = variable instanceOf constructor

如果变量是给定引用类型,则返回true:

1
2
3
console.log(person instanceOf Object); //变量是Object吗?
console.log(person instanceOf Array); //变量是Object吗?
console.log(person instanceOf RegExp); //变量是Object吗?

所有引用值都是Object实例,用instanceOf检测构造函数都会返回true。

而使用instanceOf检测原始值则返回false。

执行上下文与作用域

执行上下文(简称:上下文)的概念在JavaScript中是非常重要的。

变量或函数的上下文决定了它们可以访问哪些数据。每个上下文都有一个关联的变量对象。

这个上下文中定义的所有变量和函数都在这个对象上,虽然无法通过代码访问对象,但后台处理数据会用到。

全局上下文

全局上下文是最外层的上下文。在浏览器中,全局上下文就是我们所说的window。

使用var定义的变量会成为window对象的属性和方法,let和const则不会。

垃圾回收

不同于C、C++需要我们跟踪内存,使用指针申请、回收内容。

JavaScript和Java一样,是采用自动垃圾回收机制的语言。

每隔一段时间,会自动进行一次垃圾回收。

标记清理

JavaScript最常见的垃圾回收策略是标记清理。

当变量进入上下文时,不应该释放它们。这时会给它加上存在于上下文的标记。

当离开上下文时,不再使用这个变量,这时会给它加上离开上下文的标记。

垃圾回收程序运行时:

  1. 会标记所有变量。

  2. 然后将所有上下文中的变量和被上下文变量引用的变量去除,最后加上之前标记的变量。

  3. 随后清理内存,销毁变量并回收内存。

引用计数

另一种基本被淘汰的标记方式是引用计数。

核心思想是:每个值都记录它被引用的次数,当引用次数为0时,可以被回收。

但它存在一种缺陷:循环引用。

对象A有一个指针指向B,B引用了A。

1
2
3
4
5
6
7
8
function problem()
{
let objectA = new object(); //A被引用1次
let objectB = new object(); //B被引用1次

objectA.someOtherObject = objectB; //B被引用2次
objectB.anotherObject = objectA; //A被引用2次
}

这样,每次调用函数创建的对象值引用都不会是0。造成内存泄露。

内存管理

如果某个数据不再需要,可以将其引用设置为null解除对值的引用,下次垃圾回收时会自动回收。