JS 基础知识:对象赋值 vs. 原始赋值
介绍
学习 JS 基础知识
原始类型与对象
原始赋值和对象赋值有何不同
防止意外突变
结论
介绍
我希望自己在 JavaScript 编程生涯的早期就能够理解对象赋值的工作原理,以及它与原始赋值的区别。本文将尽可能简洁地阐述它们之间的区别!
学习 JS 基础知识
想了解更多 JS 基础知识?不妨考虑注册我的免费邮件列表!
原始类型与对象
作为回顾,让我们回顾一下 JavaScript 中的不同原始类型和对象。
原始类型: Boolean、Null、Undefined、Number、BigInt(你可能不常见到这么多)、String、Symbol(你可能不常见到这么多)
对象类型:对象、数组、日期、许多其他
原始赋值和对象赋值有何不同
原始赋值
将原始值赋给变量非常简单:将值赋给变量。我们来看一个例子。
const a = 'hello';
const b = a;
在这种情况下,a
设置为值hello
,b
也设置为值hello
。这意味着如果我们设置b
为一个新值,将保持不变;和a
之间没有关系。a
b
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。
有几种方法可以确保你的对象是唯一的。我会在这里介绍其中的一些,但请放心,这份清单并非详尽无遗。
扩展运算符 (...)
扩展运算符是创建对象或数组浅拷贝的好方法。让我们用它来复制一个对象。
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',
// },
// }
我们成功地复制了a
一层,但第二层的属性仍然引用着内存中的相同对象!因此,人们发明了进行“深度”复制的方法,例如使用类似库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',
// },
// }
但这也有缺点。序列化和反序列化不会保留像函数这样的复杂对象。
深层复制库
引入深层复制库来处理这项繁重的任务是相当常见的,尤其是在对象层次结构未知或特别深的情况下。这些库通常是一些函数,它们会沿对象树递归地执行上述某种浅层复制方法。
结论
虽然这看起来有点复杂,但只要你了解原始类型和对象的赋值方式,就能轻松搞定。不妨试试这些例子,如果你有兴趣,还可以尝试编写自己的深度复制函数!
鏂囩珷鏉ユ簮锛�https://dev.to/nas5w/js-fundamentals-object-assignment-vs-primitive-assignment-5h64