理解 JavaScript:不可变的原始值与可变的对象引用

JavaScript 中的原始值(undefained、null、布尔值、数字和字符串)与引用值(对象,数组,函数等)有着根本的区别。

存储方式
原始值
存储在stack)中的简单数据段,也就是说,它们的值直接存储在变量访问的位置。
引用值
存储在heap)中的对象,也就是说,存储在变量处的值是一个指针point),指向存储对象的内存处。

为变量赋值时,JavaScript 的解释程序必须判断该值是原始类型,还是引用类型。要实现这一点,解析程序则需尝试判断该值是否为 JavaScript 的原始类型之一,即 Undefined、Null、Boolean、Number 和 String 型。由于这些原始类型占据的空间是固定的,所以可将他们存储在较小的内存区域——栈中。这样存储便于迅速查寻变量的值。

在许多语言中,字符串都被看作引用类型,而非原始类型,因为字符串的长度是可变的。JavaScript 打破了这一传统。

如果一个值是引用类型的,那么它的存储空间将从堆中分配。由于引用值的大小会改变,所以不能把它放在栈中,否则会降低变量查寻的速度。相反,放在变量的栈空间中的值是该对象存储在堆中的地址。地址的大小是固定的,所以把它存储在栈中对变量性能无任何负面影响。如下图所示:

stack_heap
栈中的原始值与堆中的引用值
表现方式

原始值是不可更改的:任何方法都无法更改(或“突变”)一个原始值(你无法修改值本身,你只能给代表它的变量重新赋值,将原来的值覆盖)。对数字和布尔值来说显然如此——改变数字的值本身就说不通,而对字符串来说就不那么明显了,因为字符串看起来像由字符组成的数组。我们期望可以通过指定索引来修改字符串中的字符。实际上,JavaScript 是禁止这样做的,字符串中的所有的方法看上去返回了一个修改后的字符串,实际上返回的是一个新的字符串值。例如:

原始值的比较是值的比较:只有在它们的值相等时它们才相等。这对数字、布尔值、nullunderfined来说听起来有点难懂,并没有其他办法来比较他们。同样,对于字符串来说则并不明显:如果比较两个单独的字符串,当且仅当他们的长度相等且每个索引的字符都相等时,JavaScript 才认为它们相等。

引用值和原始值不同,首先,它们是可变的——它们的值是可修改的:

引用值的比较并非值的比较:即使两个引用值包含同样的属性及相同的值,它们也是不相等的。各个索引元素完全相等的两个数组也不相等:

我们通常将对象称为引用类型(reference type),以此来和 JavaScript 的基本类型区分开来。依照术语的叫法,对象值都是引用(reference),对象的比较均是引用的比较:当且仅当它们引用同一个基对象时,它们才相等。并且,修改引用值时,基对象也会被修改

如上代码所示,将对象(或数组)赋值给一个变量,仅仅是赋值的引用值:对象本身并没有复制一次。如果你想得到一个对象或数组的副本,则必须显式复制对象的每个属性或数组的每个元素,如下代码:

同样的,如果我们想比较两个单独的对象或者数组,则必须比较它们的属性或元素:

文后小结

在使用的时候,需要注意修改引用对象,基对象也会被修改,如果你想将一个原始的数据对象暂存在本地变量中,应该通过循环来完成数组复制,否则会引起不必要的麻烦。

发表评论

电子邮件地址不会被公开。 必填项已用*标注