JavaScript:值类型与引用类型的本质区别

掌握了值类型和引用类型的区别,你才能真正理解变量、赋值、函数传参等行为。

简单来说:

  • 值类型 (Value Types):复制的是值本身。像给朋友一张纸,上面写着 "100",他修改自己的纸,你的纸还是 "100"。

  • 引用类型 (Reference Types):复制的是指向数据的地址(引用)。像给朋友一把你家的钥匙,他用钥匙进你家把电视换了,你回家看到的也是新电视。

下面我们来详细分解。


一、两大类型

JavaScript 的所有数据类型都属于这两类之一。

1. 值类型 (基本数据类型 / Primitive Types)
  • 包含StringNumberBooleanUndefinedNullSymbolBigInt (共7种)

  • 特点:

    • 存储位置: 它们的值直接存储在栈 (Stack)内存中。栈内存空间小,但访问速度快。

    • 不可变性 (Immutability): 你不能改变一个基本类型的值。例如,你不能改变数字 5 本身,或者字符串 "hello" 的某个字符。所有看起来像修改的操作,实际上都是创建了一个新的值。

    • 赋值/复制: 当你把一个值类型的变量赋给另一个变量时,计算机会为新变量分配一个全新的内存空间,然后把原始值完整地复制一份过来。这两个变量从此完全独立,互不影响

代码示例:

let a = 100;
let b = a; // 复制 a 的值 (100) 给 b

console.log(`a = ${a}, b = ${b}`); // a = 100, b = 100

// 修改 b 的值
b = 200;

console.log(`a = ${a}, b = ${b}`); // a = 100 (a 没有任何变化), b = 200

内存示意图:

a 和 b 在栈内存中是两个完全独立的存储空间。

2. 引用类型 (对象类型 / Object Types)
  • 包含Object,以及它的所有子类型,如 ArrayFunctionDateRegExp 等。

  • 特点:

    • 存储位置: 它的数据本身(比如对象的属性和值)存储在堆 (Heap)内存中。堆内存空间大,可以存储复杂和大小不定的数据。而变量名和指向堆中数据的内存地址(引用)则存储在栈 (Stack)中。

    • 可变性 (Mutability): 引用类型的值是可以被修改的。

    • 赋值/复制: 当你把一个引用类型的变量赋给另一个变量时,复制的是栈中的内存地址(引用),而不是堆中的实际数据。因此,这两个变量指向的是同一个堆内存中的对象。对其中任何一个变量进行操作,都会影响到另一个变量。

代码示例:

let obj1 = { name: 'Alice', age: 25 };
let obj2 = obj1; // 复制 obj1 的引用(内存地址)给 obj2

console.log(obj1.name); // 'Alice'
console.log(obj2.name); // 'Alice'

// 修改 obj2 的属性
obj2.name = 'Bob';

// 因为 obj1 和 obj2 指向同一个对象,所以 obj1 也被影响了
console.log(obj1.name); // 'Bob' 
console.log(obj2.name); // 'Bob'

内存示意图:

obj1 和 obj2 存储在栈中,但它们存的都是同一个地址,这个地址指向堆内存中唯一的一个对象。


二、函数参数传递 (最能体现区别的地方)

面试官经常会通过函数传参来考察这个知识点。

结论先行:JavaScript 中函数参数的传递,永远是“按值传递” (Pass by Value)。

这句话听起来有点绕,我们来解释:

  • 当传递值类型时,传递的是值的副本

  • 当传递引用类型时,传递的是引用的副本(地址的副本)

1. 传递值类型
function changeValue(num) {
    num = 1000; // 在函数内部,num 是 a 的一个副本
    console.log(`Inside function: num = ${num}`); // 1000
}

let a = 10;
changeValue(a);
console.log(`Outside function: a = ${a}`); // 10 (原始的 a 没有被改变)

解释a 的值 10 被复制给了参数 num。函数内部对 num 的修改,只是修改了这个副本,完全不影响外部的 a

2. 传递引用类型
function changeName(person) {
    // person 是 myPerson 引用的一个副本,它们指向同一个对象
    person.name = 'Charlie'; 
    console.log(`Inside function: person.name = ${person.name}`); // 'Charlie'
}

let myPerson = { name: 'David' };
changeName(myPerson);
console.log(`Outside function: myPerson.name = ${myPerson.name}`); // 'Charlie' (原始对象被改变了!)

解释myPerson 变量中存储的内存地址被复制给了参数 person。现在 myPerson 和 person 都指向堆中的同一个对象。通过 person.name 修改对象属性,自然会影响到 myPerson

3. 引用类型传递的一个陷阱

如果在函数内部重新赋值整个对象,而不是修改其属性,会发生什么?

function reassignObject(person) {
    // 这里,person 这个局部变量被赋予了一个全新的引用,指向一个新对象
    person = { name: 'Eve' }; 
    console.log(`Inside function reassign: person.name = ${person.name}`); // 'Eve'
}

let myPerson2 = { name: 'Frank' };
reassignObject(myPerson2);
console.log(`Outside function reassign: myPerson2.name = ${myPerson2.name}`); // 'Frank' (原始对象没有被改变!)

解释:进入函数时,person 持有和 myPerson2 相同的地址。但 person = { name: 'Eve' } 这行代码,让 person 这个变量指向了一个全新的对象。它切断了和原始对象 myPerson2 的联系。所以,这个操作只改变了函数内部的局部变量 person 的指向,而外部的 myPerson2 仍然指向它最初的对象。


总结表格

特性值类型 (Primitive Types)引用类型 (Object Types)
包含类型StringNumberBooleanNullUndefinedSymbolBigIntObjectArrayFunction, 等
存储位置值直接存在 栈 (Stack) 中数据存在 堆 (Heap) 中,引用(地址)存在 栈 (Stack) 中
赋值操作复制值的副本,两个变量完全独立复制引用的副本,两个变量指向同一个对象
函数传参传递值的副本。函数内修改不影响外部。传递引用的副本。函数内修改对象属性会影响外部,但重新赋值对象则不会。

如何判断一个类型? 一个简单的方法:除了7种基本类型之外的所有类型,都是引用类型。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值