引言
写 JS 的人都知道有字符串、数字、布尔值这些东西,但很多人从没想过一个问题:这些值在内存里是怎么放的?为什么有时候改了一个变量,另一个变量也跟着变;有时候又完全不受影响?答案就藏在数据类型的底层设计里。这篇文章不带代码,把 JS 的数据类型讲透。
一、两大类数据
JS 的数据类型分成两大阵营:原始类型和引用类型。
原始类型包括数字、字符串、布尔值、空值、未定义值,以及后来加入的符号和超大整数。它们的特点是:值本身就是数据,简单、直接、不可变。
引用类型主要指对象,包括普通对象、数组、函数、日期等等。它们的特点是:变量存的不是数据本身,而是一个地址,真正的数据放在别的地方。
这个区别看似抽象,却决定了 JS 里大量行为的走向。
二、原始类型:按值传递
原始类型的变量,就像一个盒子里直接装着东西。
你把这个变量赋给另一个变量,相当于把盒子里的东西复制一份,放进新盒子。两个盒子各装各的,互不影响。之后你改其中一个,另一个纹丝不动。
这就是所谓的按值传递。传递的是值本身,不是位置。
理解这一点,就能明白为什么改了一个数字变量,另一个数字变量不会跟着变。它们本来就是两份独立的数据。
三、引用类型:按引用传递
引用类型的变量,盒子里装的不是东西,而是一张纸条,上面写着"东西放在某处"。
你把这个变量赋给另一个变量,复制的是那张纸条,而不是东西本身。于是两个变量拿着同样的地址,指向同一个物体。你通过其中一个变量修改了物体的内容,另一个变量再去看,看到的也是改过的结果。
这就是所谓的按引用传递。传递的是地址,不是数据。
很多人遇到的"我明明只改了一个数组,怎么另一个数组也变了",根源就在这里。它们根本不是两个数组,而是两个名字指向同一个数组。
四、比较时的差异
这个区别还影响比较运算。
两个原始类型的变量比较,比的是值。值一样,就相等。
两个引用类型的变量比较,比的是地址。哪怕两个对象内容完全一样,只要它们不是同一个物体,就不相等。
这解释了一个常见困惑:为什么两个看起来一模一样的对象,判断相等时却是假。因为它们各自独立,地址不同。
五、复制时的陷阱
因为引用类型存的是地址,所以"复制"一个对象,不能简单赋值。
直接赋值,复制的是地址,两个变量还是指向同一个物体。想真正复制出一个独立的新对象,需要做深拷贝或浅拷贝。
浅拷贝只复制第一层。如果对象里还嵌套着对象,那嵌套的部分仍然共享地址。改嵌套内容,原对象也会受影响。
深拷贝则递归复制所有层级,得到一个完全独立的新对象。但深拷贝有代价:性能开销大,遇到循环引用还可能出问题。
选择哪种,取决于实际需求。不是越深越好,够用就行。
六、不可变与可变
原始类型是不可变的。你没法修改一个字符串里的某个字符,所有看起来像修改的操作,其实都是创建了一个新值。
引用类型是可变的。你可以随时往对象里加属性、删属性、改属性,对象本身还是那个对象。
这个差异带来一个重要的编程思路:把数据当作不可变的来处理,每次修改都产生新对象,而不是在原对象上改。这样做的好处是,状态变化可追踪,不容易出现意外的副作用。现代前端框架的状态管理,很多都建立在这个理念之上。
七、总结
原始类型按值传递,引用类型按地址传递。这个底层设计,决定了赋值、比较、复制、修改时的各种行为。理解了它,你就能解释那些曾经让你迷惑的现象,也能在写代码时主动规避引用共享带来的坑。这是 JS 基础里最容易被跳过、却最不该跳过的一课。