全部 / 前端 / 技术 · 2022年4月14日 0

5 – 引用类型 vs 基本类型

广告位招租 (vx: ghostcode, 备注:网站广告)

原文地址: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 时则会影响另一个。记住,浅拷贝只会拷贝第一层属性更深层的依旧是引用类型。