IObject
v0.7.2
Published
Immutable Object
Downloads
280
Readme
IObject
An Immutable Object that looks, behaves (and IS) a standard JavaScript Object
but with immutable properties.
Upon creation from an existing object, all immediate (not from prototype) enumerable properties are transferred to the IObject
.
For immutable arrays, see IArray
Why
One of the cornerstones of functional programming is immutable data structures. Using immutable data structures insures to the author and other programmers that once they have a handle to an object, it won't change beneath their feet. This is important for reasoning about code. It aids with testing, debugging and refactoring. It provides a much faster way to determine if state has changed. And it helps you write pure functions.
Native objects are an extremely popular data structure in most JavaScript programs. But they are far from immutable. One can simply assign values via myObject.foo = 100
.
Some libraries exist that provide immutable objects, such as Mori or Immutable - but they are large opinionated libraries that expose a completely different API.
IObject provides an Immutable object only, and is very light.
wc -l IObject.js
110 IObject.js
103 lines in the source file, much of which is comments and the universal module definition. IObject.min.js
is currently just 817 bytes.
How
IObject
extends Object
and adds the non-enumerable set
function for setting values (top level and deep values). Because of this approach, IObject
appears and behaves just like an object literal with the exception of how you set values.
Note: By default the object does not block standard setting of values. You may set IObject.freeze
to either SHALLOW
or DEEP
to block standard setting of values. 'SHALLOW' only blocks top-level setting of values, whereas DEEP
freezes all objects at all levels within the IObject
. It is recommended that you set IObject.freeze
to DEEP
during development to ensure you are not inadvertently setting object values directly.
API
IObject
is a standard JavaScript Object
and hence inherits all properties and methods of Object
. In addition, it has a set
function for setting top-level and deep values within the structure.
constructor
Create a new IObject
simply by calling the IObject()
function. You may optionally use the new
keyword, but it makes no difference. You may also pass in an initialization object, whose properties will be copied to the new IObject
.
var a = IObject() // creates a new empty IObject
var b = new IObject() // Same as above - just stylistic difference
var c = IObject({ a: 1, b: 2, c: 3}) // init IObject with these properties
var d = c.a + c.b + c.c // d = 6
set(<String> name, value) => IObject
Set the property indicated by name
to value
. The name
is interpreted as a "dotted path" to allow the setting of "deep properties" within the IObject
.
IObject.freeze = "DEEP" // disallow modifying IObject or anything inside
var a = IObject() // create new empty IObject
console.log(a) // { }
a.foo = "bar" // does not effect a. still { }
Notice that setting values directly does not take effect. IObject
is immutable and so does not allow changes. You must use set
which returns a new IObject
with the change in place:
var a = IObject() // create new empty IObject
var b = a.set("foo","bar") // creates a NEW IObject with "foo" set to "bar"
console.log(a) // a = { }
console.log(b) // b = { "foo": "bar" }
Now lets looks at deep properties - i.e. properties not at the top level of the IObject
, but properties on other objects that are stored within an IObject
.
var user = IObject({ id: 123, name: "glenn" }) // two top-level properties
user = user.set("address", // setting object property for "address"
{ street: "123 Main St.", city: "Yokohama", country: "Japan" })
// We access deep properties just as we would in a standard object
log(user.address.country) // Japan
// But we set them via the set() call using dotted path string which returns
// the new IObject with the changed property
user = user.set("address.city", "Fujisawa")
log(user.address.city) // Fujisawa
Note:
Child objects within IObject
are "shallow cloned" when their properties are changed, retaining the immutable nature of IObject
. When this occurs, child objects are converted to IObject
themselves. This is generally desired behavior, but be aware of it. Objects that are not changed are not converted.
One place this may bite you is in an Array
. Standard JavaScript Array
objects are mutable - if you place one into an IObject
and then attempt to change it via set
, the array will be converted into an IObject
which is probably not intended:
var user = IObject({name: "Glenn", favs: [ 2, 7, 26 ]})
log(user.favs.join(",")) // "2,7,26" as expected
user = user.set("favs.1", 5)
log(user.favs.join(",")) // TypeError: user.favs.join is not a function
If you wish to use an array as part of an immutable data structure, use the IArray - which is immutable, and can be used within an IObject
as expected:
var user = IObject({name: "Glenn", favs: IArray([ 2, 7, 26 ])})
log(user.favs.join(",")) // "2,7,26" as expected
user = user.set("favs.1", 5)
log(user.favs.join(",")) // "2,5,26"
License
See the LICENSE file for license rights and limitations (MIT).