| Level | Add | Delete | Change value | Redefine attributes |
|---|---|---|---|---|
Object.preventExtensions(o) |
no | yes | yes | yes |
Object.seal(o) |
no | no | yes | no (values still writable) |
Object.freeze(o) |
no | no | no | no |
Each level includes the one before it:
preventExtensionssets the object's[[Extensible]]tofalse. Existing properties are untouched; the prototype can't be replaced either.sealdoes that, then defines every own property (strings and symbols) asconfigurable: false.freezedoes that, then also setswritable: falseon every own data property. Accessor properties keep working: the getter and setter still run, and only the property's configuration is locked.
Object.isExtensible, isSealed and isFrozen test the levels. An empty non-extensible object counts as both sealed and frozen.
Shallow. Only the object's own properties change. Nested objects, arrays inside it, and the internal data of a Map, Set or Date stay mutable. Typed arrays with elements can't be frozen at all (TypeError).
Irreversible. There is no unfreeze; you copy instead.
Failure mode. A rejected operation throws a TypeError in strict code (modules and classes) and silently does nothing in sloppy code. Object.freeze itself returns its argument, and primitives are returned unchanged.
Use freeze for constant tables and config; use seal for a fixed shape whose values still change; use preventExtensions rarely.