原文地址:https://dev.to/bhagatparwinder/reference-vs-value-5dbg
TLDR: 在 JavaScript 中,基本数据类型是按值访问,而 Object、Array、Set、Map 是按引用访问。
什么是按值传递?
在我之前的文章中谈到了许多 JavaScript 中的类型,String、Number 或者是 Boolean 都是按值传递。那这又代表着什么呢?
let person = "Parwinder";
let human = person;
console.log(person); // Parwinder
console.log(human); // Parwinder
person = "Ricky";
console.log(person); // Ricky
console.log(human); // Parwinder
我创建了一个 person 然后赋了一个值,之后又把 person 赋给 human 但这并不意味着 human 的改变会影响 person。当我用 person 赋值给 human 时,只是把 person 代表的值拷贝一份赋给了 person 它们互不相干,这就是按值传递。
什么是按引用传递?
Object、Array、Set 和 Map 都是按引用传递而非按值。
let personObject = {
firstName: "Parwinder",
lastName: "Bhagat"
};
let humanObject = personObject;
console.log(personObject.firstName); // Parwinder
console.log(humanObject.firstName); // Parwinder
personObject.firstName = "Ricky";
console.log(personObject.firstName); // Ricky
console.log(humanObject.firstName); // Ricky
有没有注意到不同?对 personObject 的修改会影响 humanObject 的 firstName。这是因为当我用 personObject 赋值给 humanObject 时,不是为对象做了一份拷贝,而是创建了对 personObject 的引用,所以两个对象指向同一个引用,任何一个修改都会影响另一个。
按引用传递还有另一个影响,例如调用函数时,你用 String 、Number 或 Boolean 传递参数这就是按值传递,对传入函数中值的修改不会影响原来的值。然而,如果传递给函数的值是对象,在函数中对参数的修改会影响原来的值。
基本数据类型的例子
function changeValue(arg) {
arg = "This is a new value";
return arg;
}
let person = "Parwinder"
console.log(changeValue(person)); // This is a new value
console.log(person); // Parwinder
可以看到对函数中的变量/参数 arg 的修改并没有影响 person。
引用类型的例子
function changeValue(arg) {
arg.name = "Ricky";
return arg;
}
let person = {
name: "Parwinder",
age: 33
}
console.log(changeValue(person)); // { name: 'Ricky', age: 33 }
console.log(person); // { name: 'Ricky', age: 33 }
对 name 的修改影响了原来值的!
如何拷贝对象?
若你想拷贝对象的值而不是按引用传递,则需要克隆对象。你可以通过 spread(…) 操作符:
let personObject = {
firstName: "Parwinder",
lastName: "Bhagat"
};
let humanObject = { ...personObject };
console.log(personObject.firstName); // Parwinder
console.log(humanObject.firstName); // Parwinder
personObject.firstName = "Ricky";
console.log(personObject.firstName); // Ricky
console.log(humanObject.firstName); // Parwinder
从上面代码可以看出 humanObject 是 personObject 的拷贝,原因是当我对 firstName 属性做修改的时候只影响了 personObject,并没有对 humanObject 产生影响。
就那么简单?
简短的回答:不。我们上面使用 spread 操作符只是浅拷贝,浅拷贝只是赋值了对象的第一层属性,更深层的属性依旧是引用类型。
let personObject = {
firstName: "Parwinder",
lastName: "Bhagat",
vehicles: {
car: "Honda Civic",
bike: "Honda Rebel"
}
};
let humanObject = { ...personObject };
console.log(personObject.vehicles.car); // Honda Civic
console.log(humanObject.vehicles.car); // Honda Civic
personObject.firstName = "Ricky";
console.log(personObject.firstName); // Ricky
console.log(humanObject.firstName); // Parwinder
personObject.vehicles.car = "BMW X5";
console.log(personObject.vehicles.car); // BMW X5
console.log(humanObject.vehicles.car); // BMW X5
上面的例子,我只做了浅拷贝所以当对其中一个对象的 name 做修改时并没有影响另一个,但当我修改 car 时则会影响另一个。记住,浅拷贝只会拷贝第一层属性更深层的依旧是引用类型。