在计算机科学中:
- 按值传递(pass by value):函数接收参数的副本,修改副本不影响原始值。
- 按引用传递(pass by reference):函数接收对原始变量的直接引用,修改参数会直接影响原始变量。
JavaScript中:
- 基本类型(如number, string, boolean)是按值传递。函数内部对参数的修改不会影响到外部变量;
- 复杂类型(包括对象,数组、函数等)传递的是对象引用的副本(即按共享传递/按对象引用副本传递),而不是按引用传递。【实际数据存储在堆中,栈中保存的是指向这些数据的引用】
关键区别:
-
如果JavaScript是真正的按引用传递,那么在函数内重新赋值参数(如obj = {new: 'object'})会改变外部的引用,但实际不会。
-
实际传递的是引用的值(一个内存地址的副本),所以:
-
修改对象属性会影响原对象(因为通过相同的地址访问)。
-
但重新赋值参数变量不会影响外部引用(因为只是修改了局部变量指向的新地址)。
-
1.对象是通过引用的值传递的
js
// 创建一个对象
const originalData = { count: 0 };
// 复制"引用"而非值-
const instanceA = originalData;
const instanceB = originalData;
// 修改实例A会影响原始对象
instanceA.count = 5;
console.log(originalData.count); // 输出 5
console.log(instanceB.count); // 输出 5 (共享同一对象)
2. 函数是通过值传递的
在JavaScript中,所有函数参数传递都是按值传递的。但是,当传递的是对象时,传递的值实际上是对象的引用(即内存地址)的副本,而不是对象本身。
因此,对于基本类型(如number, string, boolean等),传递的是值的副本,修改参数不会影响原始值。
对于对象类型(包括数组、函数等),传递的是引用的副本,因此通过参数修改对象的属性会影响到原始对象,但如果重新赋值整个参数(即改变引用的指向)则不会影响原始对象。
📌 核心结论
类型 | 传递方式 | 修改原始值的影响 |
---|---|---|
基本类型 | 按值传递 | ❌ 不影响原变量 |
引用类型 | 按共享传递 | ✅ 修改属性影响原对象 |
❌ 重赋值不影响原引用 |
js
function updateProperty(obj) {
// 此处 obj 是原始引用的拷贝,不是原始引用本身
obj.value = 10; // ✅ 修改原对象属性
}
function replaceObject(obj) {
obj = { value: 99 }; // ❌ 重赋值不影响原引用
}
const myObj = { value: 5 };
updateProperty(myObj);
console.log(myObj.value); // 输出 10(属性被修改)
replaceObject(myObj);
console.log(myObj.value); // 输出 10(对象未被替换)
✅ 总结
JavaScript 严格遵循按值传递:
- 基本类型:传递值副本
- 引用类型:传递引用地址的副本(常被误称为"按共享传递")
3. 按值传递,用堆栈来理解
注意:在JavaScript中,变量存储分为栈(stack)和堆(heap)两部分。
基本类型值存储在栈中,而对象存储在堆中,变量名(标识符)存储在栈中,其值对于基本类型是实际值,对于对象则是堆中内存地址的引用。
例子还是第2点的例子:
const myObj = { value: 5 };
- 在堆(heap)中创建一个对象
{ value: 5 }
,假设其内存地址为0x100
。 - 在栈(stack)中为变量
myObj
分配空间,存储的是堆中对象的地址0x100
(即引用)。
此时,栈中:
变量名 | 值 |
---|---|
myObj | 0x100 |
堆中:
地址:0x100
内容:{ value: 5 }
updateProperty(myObj);
function updateProperty(obj) { obj.value = 10; }
- 调用函数时,将
myObj
的值(即地址0x100
)复制一份,传递给函数参数obj
。 - 所以在函数内部,
obj
同样指向堆中的地址0x100
。
栈(在函数执行时):
变量名 | 值 |
---|---|
myObj | 0x100 |
obj | 0x100 |
堆中:
地址:0x100
内容:{ value: 10 } // 被修改了
所以,当我们在函数内通过 obj.value = 10
修改属性时,是通过地址 0x100
找到堆中的对象并修改的,因此原对象被修改。
replaceObject(myObj);
function replaceObject(obj) { obj = { value: 20 }; }
-
传递时,将
myObj
的值(0x100)复制给obj
(函数参数)。 -
在函数内部,执行
obj = { value: 20 }
时:- 在堆中创建一个新对象
{ value: 20 }
,假设地址为0x200
。 - 将函数参数
obj
的值改为0x200
(注意:这改变的是栈中函数参数obj存储的地址,而不是原来0x100地址处的对象)。
- 在堆中创建一个新对象
此时,栈(在函数reassign内部):
变量名 | 值 |
---|---|
obj | 0x200 |
堆中:
地址:0x100 -> { value: 5 } // 未被改变
地址:0x200 -> { value: 20 }
函数执行完毕后,栈中函数参数obj
被释放,而全局变量myObj
依然指向0x100,所以原对象没有被改变。
总结:
- 在函数内部修改对象的属性,会影响到原对象,因为通过相同的引用地址操作堆中的同一个对象。
- 在函数内部重新赋值参数(即改变参数变量的指向),不会影响外部变量,因为传递的只是外部变量所存储的地址值的副本,函数内部改变的是这个副本(指向了一个新对象),而外部变量仍然指向原对象。