Immutable means the object can't change after it is created. Each tool covers a different part:
readonlyfields stop reassignment at compile time only. They are shallow: areadonly items: string[]can still be pushed to. They disappear in the JavaScript output.Readonly<T>makes every top-level property of a typereadonly. It is also compile-time only and shallow. Usereadonly string[]orReadonlyArrayfor arrays.Object.freeze(this)blocks changes at runtime. In strict mode a write throws aTypeError. It is shallow too, so freeze nested objects yourself. It also stops subclasses from adding fields aftersuper().- Operations return new instances:
money.add(other)returns a newMoney. It never changesthis.
A good recipe for a value object:
readonlyfields with primitive or readonly types- a constructor or static factory that validates
- methods that return new instances
Object.freezeif JavaScript code or untyped input could change it
Why bother? An immutable object can be shared between requests and cached safely. No caller can change it under another caller.
readonlyandReadonly<T>are compile-time checks only.- All of these tools are shallow, so nested arrays and objects need their own protection.
Object.freezeblocks changes at runtime and throws in strict mode.- Value object methods should return new instances instead of changing
this. - Immutable objects are safe to share and cache.