JavaScript - 可变性 vs 不可变性
如果您是一名 Web 开发者,您一定见过 JavaScript 中的可变对象和不可变对象这两个术语。但这些词究竟是什么意思?它们为什么重要?本节将帮助您理解。
什么是可变对象和不可变对象
它们之间的主要区别
如何创建和修改它们
在代码中使用每种类型的优缺点
这些知识对于编写更简洁、更安全、更可靠的 JavaScript 代码非常有帮助。
可变对象
JavaScript 中的可变对象是指创建后可以更改的对象,包括数组、对象、函数和日期。要添加、删除或修改这些对象中的值,请使用括号表示法 (object["property"]) 或点表示法 (object.property)。
但是,更改这些对象有时可能会导致问题:它可能会无意中影响代码的其他部分,使调试更加困难,并占用更多内存。因此,即使您可以直接更改可变对象,通常最好避免这样做,以保持代码更简单、更可靠。
可变数据类型允许您更改它。可变性使您可以更改现有值而无需创建新值。
对于每个对象,都会在堆栈中添加一个指针,指向堆中的对象。请考虑以下代码 -
const student = {
name: "Aarav",
age: 15,
hobbies: ["drawing", "playing guitar", "cycling"]
};
在堆栈上,您将看到 student,它是指向堆上实际对象的指针。
const student2 = student; console.log(student); console.log(student2);
当 student 被赋值给 student2 时,堆栈中会添加一个额外的指针。现在,这些指针指向堆上的单个项。
引用数据复制的是指针,而不是值。
student2.age = 25; console.log(student); console.log(student2);
更改 student2 的年龄会影响 student 对象的年龄。您知道这是因为它们都指向同一个对象。
克隆对象属性
要克隆对象的属性,请使用带有扩展运算符的 Object.assign() 函数。这允许您更新克隆对象的属性,同时保持原始对象的属性不变。
Object.assign() 函数的工作原理
object.assign 方法将属性从一个对象(源)复制到另一个对象(目标),并返回更新后的目标对象。
语法如下 -
Object.assign (target, source)
该方法接受两个参数:target 和 source。target 是接收新属性的对象,而 source 是属性的来源。目标可以是空对象 {}。
当源和目标具有相同的键时,源对象将覆盖目标上该键的值。
const student = {
name: "Aarav",
age: 15,
hobbies: ["drawing", "playing guitar", "cycling"]
}
const student2 = Object.assign({}, student);
student 对象的属性被复制到一个空目标中。
student2 现在拥有自己的属性。您可以通过修改其任何特征的值来体现这一点。此调整不会影响 student 对象的属性值。
student2.age = 25; console.log(student); console.log(student2);
student2.age 的值被修改为 25,这与 student.age 的值无关,因为它们都有各自的属性。
不可变对象
不可变对象是指创建后无法修改的对象。在 JavaScript 中,字符串、数字、布尔值、null 和 undefined 都是不可变的。您不能直接更改它们的值或特性。
相反,您必须创建一个包含所需更改的新对象。例如,您可以合并两个字符串或添加两个整数,但这会产生一个新的字符串或数字,而不会改变原始字符串或数字。
不可变对象比可变对象具有多个优势。它们可以减少副作用,使您的代码更易于理解和维护,并提高性能。
创建不可变对象
在 JavaScript 中有几种构建不可变对象的方法。一种是使用 Object.freeze() 方法,该方法会阻止对现有对象的更改。例如,如果您冻结一个数组或对象,然后尝试对其进行编辑,您将收到错误或没有任何效果。
另一种是使用 const 关键字,它会创建一个无法重新赋值的常量变量。例如,如果您将一个字符串或整数声明为 const,然后尝试对其进行重新赋值,您将收到错误。但是 const 只是使变量不可变,而不是它所引用的对象。
让我们考虑一个例子 -
const num = 46; const newNum = num;
通过查看上面的代码,num 已被重新赋值给 newNum。num 和 newNum 现在具有相同的值:46。更改 newNum 的值不会影响 num 的值。
let student1 = "Aarti"; let student2 = student1;
上面的代码创建了一个名为 student1 的变量,并将其赋值给 student2。
student1 = "Saurabh" console.log(student1); console.log(student2);
将 student1 更改为 Saurabh 不会影响 student2 的初始值。这表明,在原始数据类型中,实际值是重复的,因此每个值都有自己的值。Student1 和 student2 具有不同的堆栈内存地址。
堆栈遵循后进先出原则。最先进入堆栈的对象最后离开,反之亦然。访问堆栈中保存的对象很简单。
修改不可变对象
不可变对象无法直接更改,因此您必须使用其他技术来编辑它们。一种选择是使用展开运算符 (...),它将对象或数组的属性或元素复制到一个新对象中。例如,您可以使用展开运算符生成一个包含新项的新数组,或一个包含新属性的新对象,而无需更改原始对象。
另一种选择是使用 Object.assign() 方法,该方法将一个或多个源对象的属性转移到目标对象。例如,您可以使用 Object.assign() 创建一个继承两个现有对象的属性的新对象,而无需修改它们。
使用展开运算符
对于数组:假设您有一个名为fruits的数组 -
const fruit = ["apple", "banana", "orange"];
使用附加项创建新数组,而不修改或更改水果 -
const newFruits = [...fruits, "grape"]; console.log(newFruits); // 原始数组保持不变 console.log(fruits);
以上代码的结果如下 -
[ 'apple', 'banana', 'orange', 'grape' ] [ 'apple', 'banana', 'orange' ]
对于对象:假设您有一个名为 person 的对象 -
const person = { name: "Maya", age: 30 };
创建一个具有附加属性(例如 country)的新对象,但不修改 person 本身 -
const newPerson = { ...person, country: "India" };
console.log(newPerson);
// 原始对象保持不变
console.log(person);
以下是上述代码的输出 -
{ name: 'Maya', age: 30, country: 'India' }
{ name: 'Maya', age: 30 }
使用 Object.assign()
对于对象: Object.assign() 通过组合多个对象的属性来创建一个新对象。假设您有两个对象:person 和 additionalInfo。
const person = { name: "Maya", age: 30 };
const additionalInfo = { country: "India", hobby: "reading" };
使用 person 和 additionalInfo 的属性创建新对象 -
const newPerson = Object.assign({}, person, additionalInfo);
console.log(newPerson);
// 原始对象保持不变
console.log(person);
以下代码的输出为 -
{ name: "Maya", age: 30, country: "India", hobby: "reading" }
{ name: "Maya", age: 30 }
不可变性的好处
不可变性可以为您的 Web 开发项目带来诸多优势 -
其中之一就是避免副作用,即程序状态或行为发生您不希望或无法确定的变化。
例如,如果您将一个可变对象作为参数传递给一个函数,该函数可能会更改该对象,从而影响依赖于它的其他代码部分。
但是,如果您提供一个不可变对象,函数就无法更改它,从而避免了这个问题。另一个好处是性能提升,尤其是在使用虚拟 DOM 的框架中,例如 React。
虚拟 DOM 是真实 DOM 的副本,它会在内存中更新,然后与实际 DOM 进行比较以应用更改。
如果使用不可变对象,比较会更快、更高效,因为您只需检查引用而不是值。
不可变性的缺点
不可变性有各种缺点,您应该注意 -
其中之一就是增加内存消耗,因为每次想要更改对象时都必须创建新的对象。
这也会影响垃圾回收,垃圾回收是通过删除不需要的对象来清理内存的过程。
例如,如果在循环中生成大量临时对象,则可能会造成内存过载并减慢垃圾回收速度。
另一个缺点是,创建和修改不可变对象需要使用额外的方法或操作符,这会使代码更加冗长且难以阅读。
为了获得相同的结果,您可能需要编写更多行代码或使用分层扩展操作符。
可变性与不可变性
以下是表格形式中可变性与不可变性之间的基本区别 -
| 方面 | 可变性 | 不可变性 |
|---|---|---|
| 含义 | 创建后即可更改值。 | 创建后,值将无法更改。 |
| 示例 | 数组或对象(例如,let numbers = [1, 2, 3];') | 字符串或数字(例如,const name = "Amit";') |
| 可以更改吗? | 可以,您可以更改或修改它。 | 不,您不能直接更改它。 |
| 如何更改? | 您可以修改原始值。 | 您可以创建一个新值,而不是更改原文。 |
| 示例代码(修改) | 'numbers.push(4);' | 'const newName = name + " Deepak";' |

