本文深入解析JavaScript中原始值与复杂值的本质区别。涵盖Undefined、Null等五种原始类型的特性,以及Object复杂值的引用机制。详细阐述两者在栈/堆内存中的存储方式、按值与按引用的访问差异,并通过typeof运算符、相等性比较及动态属性示例,厘清值传递与引用传递的核心逻辑。
JavaScript原始值与复杂值概述
前言
JavaScript的数据类型主要划分为两类:原始类型(Primitive Type)与引用类型(Reference Type)。其中,原始类型又被称为基本类型或简单类型,具体包含Undefined、Null、Boolean、Number和String这五种;而引用类型则被称为复杂类型,在JavaScript中主要指代Object。与这两种类型相对应,其对应的数据值也被分别称为原始值和复杂值。
特性概述
原始值的特性与类型
原始值(Primitive Value)
简而言之,原始值是固定且简单的数值,它们直接存储在内存的栈(Stack)区域中的简单数据段内。这意味着变量的访问位置直接存储了这些值本身。
原始值代表了JavaScript中可用数据或信息的最基础或最简形式。之所以称为“原始值”,是因为它们是不可再细分的。例如,数字就是数字,字符串就是字符串,布尔值非true即false,而null和undefined则保持其原始状态。这些值本身结构简单,无法表示由其他值组合而成的复杂结构。
哪些类型属于原始类型?
原始类型(Primitive Type)具体包含以下五种:Undefined、Null、Boolean、Number、String。
我们可以利用typeof运算符来检测一个变量是否属于特定的类型范围。
typeof运算符的使用与注意事项
typeof运算符
对变量或常量使用typeof运算符,将返回以下字符串之一:

注意:
1. 该运算符返回的结果类型为字符串。
2. 与原始类型相比,typeof对null的处理较为特殊。使用typeof(null)会返回"object",这里我们将null视为object类型的占位符。
复杂值的内存存储机制
复杂值
复杂值可以由多种不同类型的JavaScript对象组合而成。由于复杂对象可以包含任意类型的值,其内存占用大小是不确定的,这与具有特定已知值的原始值不同。
存储方式
栈存储
由于原始值占据固定的空间,属于简单的数据段,为了提升变量查询效率,它们被存储在栈(Stack)内存中。
堆存储
因为复杂值的大小是动态变化的,若将其存放在栈中会降低变量查询速度,因此复杂值存储在堆(Heap)内存中。此时,变量中存储的值实际上是一个指向堆内存中对象所在位置的指针。

值的访问与比较方式
访问方式
按值访问
原始值作为不可细分的值进行存储和操作,当引用它们时,实际上是转移了其具体的值。
var myString = 'foo';
var myStringCopy = myString;
var myString = null;
console.log(myString,myStringCopy);//null,'foo'
引用访问
复杂值是通过引用(Reference)进行存储和操作的,而非实际的值本身。当创建一个包含复杂对象的变量时,该变量的值实际上是内存中的一个引用地址。要访问复杂对象时,需通过变量名或对象属性名,利用内存中的引用地址来获取该对象的值。
var myObject = {};
var copyOfMyObject = myObject;//没有复制值,而是复制了引用
myObject.foo = 'bar';//操作myObject中的值
//现在如果输出myObject和copyOfMyObject,则都会输出foo属性,因为它们引用的是同一个对象
console.log(myObject,copyOfMyObject);//Object{foo="bar"}
比较方式
原始值采用值比较,而复杂值采用引用比较。复杂值仅在引用指向同一个对象(即拥有相同的内存地址)时才相等。即使两个变量包含相同内容的对象,只要它们指向不同的内存地址,彼此就不相等。
var price1 = 10;
var price2 = 10;
var price3 = new Number('10');
var price4 = price3;
console.log(price1 == price2);//true
console.log(price1 == price3);//false
price4 = 10;
console.log(price4 == price3);//true
console.log(price4 === price3);//false
var objectFoo = {same:'same'};
var objectBar = {same:'same'};
console.log(objectFoo == objectBar);//false
var objectA = {foo: 'bar'};
var objectB = objectA;
console.log(objectA == objectB);//true
动态属性与包装类型
动态属性
对于复杂值,可以为其添加、修改或删除属性和方法;但原始值不允许添加属性和方法。
复杂值支持动态对象属性,因为我们可以先定义对象,再创建引用,随后更新对象,所有指向该对象的变量都会同步获得更新。当一个新变量指向现有的复杂对象时,它并没有复制该对象,而是共享了引用。这就是复杂值有时被称为引用值的原因。复杂值可以根据需求拥有任意多个引用,即使对象内容改变,这些引用始终指向同一个对象。
var str = 'test';
str.property = true;
console.log(str.property);//undefined
var objA = {property: 'value'};
var pointer1 = objA;
var pointer2 = pointer1;
objA.property = null;
console.log(objA.property,pointer1.property,pointer2.property);//null null null
包装类型
当原始值被当作构造函数创建的对象来使用时,JavaScript会自动将其转换成一个对象,以便利用对象的特性和方法。使用完毕后,系统会丢弃对象性质,并将其还原为原始值。