JS 基础知识:对象赋值 vs. 原始赋值简介学习 JS 基础知识原始赋值 vs. 对象原始赋值和对象赋值的区别防止意外变异结论

2025-06-10

JS 基础知识:对象赋值 vs. 原始赋值

介绍

学习 JS 基础知识

原始类型与对象

原始赋值和对象赋值有何不同

防止意外突变

结论

介绍

我希望自己在 JavaScript 编程生涯的早期就能够理解对象赋值的工作原理,以及它与原始赋值的区别。本文将尽可能简洁地阐述它们之间的区别!

学习 JS 基础知识

想了解更多 JS 基础知识?不妨考虑注册我的免费邮件列表

原始类型与对象

作为回顾,让我们回顾一下 JavaScript 中的不同原始类型和对象。

原始类型: Boolean、Null、Undefined、Number、BigInt(你可能不常见到这么多)、String、Symbol(你可能不常见到这么多)

对象类型:对象、数组、日期、许多其他

原始赋值和对象赋值有何不同

原始赋值

将原始值赋给变量非常简单:将值赋给变量。我们来看一个例子。

const a = 'hello';
const b = a;
Enter fullscreen mode Exit fullscreen mode

在这种情况下,a设置为值hellob也设置为值hello。这意味着如果我们设置b为一个新值,将保持不变;a之间没有关系ab

const b = 'foobar';
console.log(a); // "hello"
console.log(b); // "foobar"
Enter fullscreen mode Exit fullscreen mode

对象分配

对象赋值的工作方式有所不同。将对象赋值给变量的操作如下:

  • 在内存中创建对象
  • 将内存中对象的引用赋给变量

这有什么大不了的?让我们来探讨一下。

const a = { name: 'Joe' };
const b = a;
Enter fullscreen mode Exit fullscreen mode

第一行在内存中创建对象,然后将该对象{ name: 'Joe' }引用赋给变量a。第二行将内存中同一个对象的引用赋给!b

因此,为了回答“为什么这是一件大事”的问题,让我们改变分配给的对象的属性b

b.name = 'Jane';
console.log(b); // { name: "Jane" }
console.log(a); // { name: "Jane" }
Enter fullscreen mode Exit fullscreen mode

没错!由于ab被赋予了对内存中同一个对象的引用,因此改变 的属性实际上就是改变 和指向的b内存对象的属性。ab

更详细地说,我们也可以在数组中看到这一点。

const a = ['foo'];
const b = a;

b[0] = 'bar';

console.log(b); // ["bar"]
console.log(a); // ["bar"]
Enter fullscreen mode Exit fullscreen mode

这也适用于函数参数!

这些赋值规则也适用于将对象传递给函数的情况!请看以下示例:

const a = { name: 'Joe' };

function doSomething(val) {
  val.name = 'Bip';
}

doSomething(a);
console.log(a); // { name: "Bip" }
Enter fullscreen mode Exit fullscreen mode

这个故事的寓意是:除非有意为之,否则要小心改变传递给函数的对象(我认为在很多情况下你都不会真正想这样做)。

防止意外突变

很多情况下,这种行为是我们所期望的。指向内存中的同一个对象有助于我们传递引用并做一些巧妙的事情。然而,这并不总是我们所期望的行为,当你无意中修改对象时,最终可能会出现一些非常令人困惑的 bug。

有几种方法可以确保你的对象是唯一的。我会在这里介绍其中的一些,但请放心,这份清单并非详尽无遗。

扩展运算符 (...)

扩展运算符是创建对象或数组拷贝的好方法。让我们用它来复制一个对象。

const a = { name: 'Joe' };
const b = { ...a };
b.name = 'Jane';
console.log(b); // { name: "Jane" }
console.log(a); // { name: "Joe" }
Enter fullscreen mode Exit fullscreen mode

关于“浅”复制的说明

理解浅复制与深复制非常重要。浅复制对于只有一层深度的对象很有效,但对于嵌套对象则会出现问题。让我们使用以下示例:

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',
//   },
// }
Enter fullscreen mode Exit fullscreen mode

我们成功地复制了a一层,但第二层的属性仍然引用着内存中的相同对象!因此,人们发明了进行“深度”复制的方法,例如使用类似库deep-copy或序列化和反序列化对象。

使用 Object.assign

Object.assign可以用来基于另一个对象创建一个新对象。语法如下:

const a = { name: 'Joe' };
const b = Object.create({}, a);
Enter fullscreen mode Exit fullscreen mode

注意;这仍然是浅拷贝!

序列化和反序列化

深度复制对象的一种方法是序列化和反序列化对象。一种常见的方法是使用JSON.stringifyJSON.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',
//   },
// }
Enter fullscreen mode Exit fullscreen mode

但这也有缺点。序列化和反序列化不会保留像函数这样的复杂对象。

深层复制库

引入深层复制库来处理这项繁重的任务是相当常见的,尤其是在对象层次结构未知或特别深的情况下。这些库通常是一些函数,它们会沿对象树递归地执行上述某种浅层复制方法。

结论

虽然这看起来有点复杂,但只要你了解原始类型和对象的赋值方式,就能轻松搞定。不妨试试这些例子,如果你有兴趣,还可以尝试编写自己的深度复制函数!

鏂囩珷鏉ユ簮锛�https://dev.to/nas5w/js-fundamentals-object-assignment-vs-primitive-assignment-5h64
PREV
学习非常有用但经常被忽视的 JavaScript 内置 Set 对象 Set 对象结论
NEXT
关于科技博客:只要你写,他们就会来