解释可变和不可变对象之间的差异
不可变性是函数式编程的一个核心原则,对面向对象的程序也有很多好处。 可变对象是一个可以在创建后修改状态的对象。 不可变对象是一个在创建后无法修改状态的对象。
JavaScript 中有什么是不可变对象的例子?
在 JavaScript 中,一些内置类型 (数字,字符串) 是不可变的,但自定义对象通常是可变的。
一些内置不可变的 JavaScript 对象是 Math
, Date
。
这里有几种方法在普通 JavaScript 对象上添加/模拟不可变性。
对象常量属性
通过将writable: false
和 configurable: false
, 你基本上可以创建一个不变值(无法更改, 重新定义或删除) 作为对象属性, 例如:
防止扩展
如果你想阻止一个对象被添加新的属性,但在其他方面不影响该对象的其他属性,可以调用Object.preventExtensions(...)
:
在非严格模式下,b
的创建静默失败了。 在严格模式下,它会抛出一个 TypeError
。
密封
Object.seal()
创建一个 "密封 "的对象,这意味着它采用一个现有的对象,并基本上对其调用Object.preventExtensions()
,但也将其所有现有属性标记为 configurable: false
。
因此,你不仅不能再添加任何属性,而且也不能重新配置或删除任何现有的属性(尽管你仍然可以修改其值)。
冻结
Object.freeze()
创建一个冻结的对象,这意味着它采用一个现有的对象,并基本上对其调用Object.seal()
,但它也将所有 "数据访问器 "属性标记为writable: false
,所以它们的值不能被改变。
这种方法是你对一个对象本身所能达到的最高级别的不变性,因为它可以防止对该对象或其任何直接属性的任何改变(尽管如上所述,任何被引用的其他对象的内容都不会受到影响)。
冻结对象不允许将新属性添加到对象中,并阻止用户删除或更改现有属性。 Object.freeze()
保留了对象的可枚举性、可配置性、可写入性和原型。 它返回传递的对象(第一个参数),不会创建一个冻结的副本。
不可变性的利弊是什么?
优点
- 更容易的变化检测:对象的平等性可以通过一种高效和简单的方式即引用的平等性来确定。 这有助于 React 和 Redux 中比较对象的差异。
- 复杂程度较低:具有不可变对象的方案不那么复杂而难以思考, 因为你不需要担心一个对象可能会随着时间而演变。
- 通过引用轻松共享:一个对象的一个副本和另一个一样好,所以你可以缓存对象或多次重复使用同一个对象。
- 线程安全:在多线程环境中,可以安全地在线程之间使用不可变的对象,因为在其他同时运行的线程中不存在修改这些对象的风险。
- 需要更少的内存:使用库,例如 Immer 和 Immutable.js, 对象是使用结构共享进行修改, 具有相似结构的多个对象需要较少内存。
- 不需要防御性拷贝:当不可变的对象从函数中返回或传递给函数时,不再需要防御性拷贝,因为不可变的对象不可能被它所修改。
缺点
- 复杂,要自己创建:对不可变的数据结构及其操作的天真实现会导致性能极差,因为每次都要创建新的对象。 建议利用库来建立高效的不可改变的数据结构和运作,从而对结构共享产生影响。
- 潜在的负面性能:分配(和减少分配)许多小对象,而不是修改现有对象,可能会对性能产生影响。 内存分配器或垃圾收集器的复杂性通常取决于堆上对象的数量。
- 循环数据结构的复杂性:像图表这样的循环数据结构难以构建。 如果您有两个在初始化后无法修改的对象,如何让它们指向对方?