本文解析 JavaScript 中原始类型与对象类型的赋值差异。原始类型赋值传递值,互不影响;对象类型赋值传递引用,修改会相互影响。文章介绍了使用展开运算符、Object.assign、JSON 序列化及第三方库实现浅拷贝与深拷贝的方法,帮助开发者避免意外修改数据。
介绍
本文旨在简明阐述 JavaScript 中对象类型与原始类型赋值的区别及其工作原理,这是初学者应掌握的核心概念。
JS 中的原生类型和对象类型
JavaScript 包含多种原始类型,包括 Boolean、Null、Undefined、Number、BigInt(较少见)、String 和 Symbol(较少见)。对象类型则包括 Object、Array、Date 及其他衍生类型。
原生类型赋值和对象类型赋值有什么不同?
原生类型赋值
将原始类型的值赋给变量十分直接,参考以下示例。
const a = 'hello';
const b = a;在此示例中,变量 a 被赋值为 'hello',变量 b 同样被赋值为 'hello'。若修改 b 的值,a 的值保持不变,两者之间没有任何关联。
const b = 'foobar';
console.log(a); // "hello"
console.log(b); // "foobar"对象类型赋值
对象类型的赋值机制不同,将对象赋给变量包含两个步骤:
- 在内存中创建一个对象
- 将该对象的引用赋值给变量
具体过程如下:
const a = { name: 'Joe' };
const b = a;第一行代码在内存中创建对象 { name: 'Joe' },并将该对象的引用赋给变量 a。第二行代码将同一对象的引用再次赋给变量 b。
接下来,修改分配给 b 的对象的属性:
b.name = 'Jane';
console.log(b); // { name: "Jane" }
console.log(a); // { name: "Jane" }由于 a 和 b 指向内存中同一个对象的引用,修改 b 的属性会同时影响 a 和 b。
数组中也存在相同的变化:
const a = ['foo'];
const b = a;
b[0] = 'bar';
console.log(b); // ["bar"]
console.log(a); // ["bar"]这种变化同样适用于函数参数。
将对象传递给函数时,同样遵循上述赋值规则。参考以下示例:
const a = { name: 'Joe' };
function doSomething(val) {
val.name = 'Bip';
}
doSomething(a);
console.log(a); // { name: "Bip" }修改传递给函数的对象值时需格外小心,除非这是预期行为(通常并非如此)。
防止意外的修改
在大多数情况下,这种机制符合预期,允许将同一对象引用赋给不同变量。但有时会导致意想不到的结果,修改对象时可能引发令人困惑的 bug。
有几种方法可以防止这种行为,本文将介绍其中几种,但并非全部。
JavaScript 展开操作符(...)
展开操作符是进行对象或数组浅拷贝的有效方法。
const a = { name: 'Joe' };
const b = { ...a };
b.name = 'Jane';
console.log(b); // { name: "Jane" }
console.log(a); // { name: "Joe" }有关“浅拷贝”
理解浅拷贝与深拷贝至关重要。对于单层对象,浅拷贝没有问题;但对于嵌套对象,浅拷贝会存在问题,参考以下示例:
const a = {
name: 'Joe',
dog: {
name: 'Daffodil',
},
};
const b = { ...a };
b.name = 'Pete';
b.dog.name = 'Frenchie';
console.log(a);
// {
// name: 'Joe',
// dog: {
// name: 'Frenchie',
// },
// }展开操作符仅实现对象第一层深度的拷贝,第二层属性在内存中仍指向同一对象。为解决此问题,可采用“深拷贝”方法,例如使用第三方库 deep-copy,或对对象进行序列化和反序列化。
使用 Object.assign
Object.assign 可用于基于一个对象创建新对象,使用方式如下:
const a = { name: 'Joe' };
const b = Object.create({}, a);注意,这仍然是浅拷贝。
序列化和反序列化
对对象进行序列化和反序列化可实现深拷贝,最常见的方法是使用 JSON.stringify 和 JSON.parse。
const a = {
name: 'Joe',
dog: {
name: 'Daffodil',
},
};
const b = JSON.parse(JSON.stringify(a));
b.name = 'Eva';
b.dog.name = 'Jojo';
console.log(a);
// {
// name: 'Joe',
// dog: {
// name: 'Daffodil',
// },
// }
console.log(b);
// {
// name: 'Eva',
// dog: {
// name: 'Jojo',
// },
// }然而,该方法存在缺点,序列化和反序列化不会保留复杂类型的对象,例如函数。
实现深拷贝的第三方库
在实际工作中,使用第三方库实现深拷贝很常见,特别是当对操作对象的结构和层级不清楚时。这些库提供的函数通常通过递归操作实现上述对象的浅拷贝功能。
结论
本文讨论的细节可能看似复杂,但若了解 JS 中原生类型与对象类型的赋值原理,便易如反掌。建议尝试上述示例,也可尝试自行实现对象深拷贝函数。
到此这篇关于 JS 基本原理:对象类型赋值和原生类型赋值的文章就介绍到这了,更多相关 js 对象类型赋值内容请搜索本站以前的文章或继续浏览下面的相关文章希望大家以后多多支持本站!