在面向对象或函数式编程中,不可变对象指的是在创建后它的状态不能改变的对象。相应的,状态可以被改变的对象,则被称为可变对象。
JavaScript 默认对象
JavaScript 只有一种结构,那就是对象。那么在 JavaScript 中的默认对象具有什么特性呢?
1 | const person = { |
上例声明了一个 person
对象。需要注意的是 const person = {}
是对象声明的语法糖,相当于 const person = new Object()
。JavaScript 的 Object
提供了一个 getOwnPropertyDescriptor()
的方法,该方法返回指定对象上一个自有属性对应的属性描述符)。我们接下来查看一下 person
对象 name
属性的属性描述符:
1 | console.log(Object.getOwnPropertyDescriptor(person, 'name')); |
JavaScript 对象里目前存在的属性描述符有两种主要形式:数据描述符和存取描述符。数据描述符是一个具有值的属性,该值可能是可写的,也可能不是可写的。存取描述符是由 getter-setter 函数对描述的属性。
数据描述符和存取描述符均具有以下可选键值:
键值 | 描述 |
---|---|
value | 该属性对应的值。可以是任何有效的 JavaScript 值。默认为 undefined |
writable | 当且仅当该属性的writable 为true 时,value 才能被赋值运算符改变。默认为 false |
enumerable | 当且仅当该属性的enumerable 为true 时,该属性才能够出现在对象的枚举属性中。默认为 false |
configurable | 当且仅当该属性的 configurable 为 true 时,该属性描述符才能够被改变,同时该属性也能从对应的对象上被删除。默认为 false |
存取描述符同时具有以下可选键值:
键值 | 描述 |
---|---|
get | 一个给属性提供 getter 的方法,如果没有 getter 则为 undefined 。该方法返回值被用作属性值。默认为 undefined |
set | 一个给属性提供 setter 的方法,如果没有 setter 则为 undefined 。该方法将接受唯一参数,并将该参数的新值分配给该属性。默认为 undefined |
需要注意的是:如果一个描述符不具有 value, writable, get 和 set 任意一个关键字,那么它将被认为是一个数据描述符。如果一个描述符同时有(value 或 writable)和( get 或 set)关键字,将会产生一个异常。
下表为描述符可同时具有的键值:
configurable | enumerable | value | writable | get | set | |
---|---|---|---|---|---|---|
数据描述符 | Yes | Yes | Yes | Yes | No | No |
存取描述符 | Yes | Yes | No | No | Yes | Yes |
可以看到,我们一般创建一个对象,其数据描述符默认均为 true。
JavaScript 中的不可变对象
在 JavaScript 对对象的操作无非是增删改查,而不可变对象的指的是创建后它的状态不能改变的对象。
在数据描述符中对应可以找到:writable
可以控制改的属性,configurable
控制删的属性。
而对于增的操作只能通过 Object
提供的 preventExtensions
来改变了。Object.preventExtensions()
方法让一个对象变的不可扩展,也就是永远不能再添加新的属性。
Object.defineProperty()
方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象。我们可以通过这个方法来设置 writable
和 configurable
属性。
我们接下来重写一下 person
对象:
1 | const person = {}; |
从上例可以看到,更改 person
对象的 name 属性为 B、删除 person
对象的 name 属性和给 person
对象添加 height 属性均没有操作成功。
实现一个不可变对象构造器
在 facebook 的实现的不可变对象库 Immutable 中,Map
是相当于 JavaScript 中的对象。这里我们也实现一个 ImmutableMap
,这里假设传入的参数均为对象。
1 | function ImmutableMap(object) { |
可以看到我们的 ImmutableMap
实现了不可变对象的需求。
在另一个不可变对象库 seamless-immutable 中使用了 JavaScript Object
提供的方法 freeze
。
Object.freeze()
方法可以冻结一个对象,冻结指的是不能向这个对象添加新的属性,不能修改其已有属性的值,不能删除已有属性,以及不能修改该对象已有属性的可枚举性、可配置性、可写性。也就是说,这个对象永远是不可变的。该方法返回被冻结的对象。
Object.freeze()
函数执行下面的操作:
- 使对象不可扩展,这样便无法向其添加新属性。
- 为对象的所有属性将 configurable 特性设置为 false。
- 为对象的所有数据属性将 writable 特性设置为 false。
另外 Object
还提供一个 seal
方法。跟 freeze
相比,不会将 writable
特性设置为 false ,也就是说用 Object.seal()
密封的对象可以改变它们现有的属性。
参考资料: