Hello everyone!
I’d like to share an early-stage idea for a reversible Object.seal()
and Object.freeze()
API, using a secret key mechanism (such as a Symbol
) to restore the object’s original mutability.
Proposal overview:
const secret = Symbol();
// Seals or freezes with an optional secret
Object.seal(obj, secret); // reversible seal
Object.freeze(obj, secret) // reversible freeze
// Later, restores previous state
Object.unseal(obj, secret)
Object.unfreeze(obj, secret)
If the correct secret is not provided, Object.unseal()
and Object.unfreeze()
throw an error.
Use cases
- Frameworks or libraries that want to protect an object during validation or construction, and then unlock it.
- Test environments or mutation guards.
- Developer tools and instrumented runtimes.
Use case example
// my-api.js
function MyApi() {
const items = []
const secret = Symbol()
Object.freeze(items, secret)
return {
items,
add: (item) => {
Object.unfreeze(items, secret)
items.push(item)
Object.freeze(items, secret)
},
}
}
// consumer.js
const api = MyApi()
api.add('item1')
console.log(api.items) // ['item1']
api.items.push('item3') // Throws an error
Why not using Proxy
?
To replicate this behavior using a Proxy
, you need to intercept and restrict all mutating operations manually:
function MyApi() {
const items = []
const proxy = new Proxy(items, {
get(target, prop, receiver) {
return Reflect.get(target, prop, receiver)
},
set() {
return false
},
deleteProperty() {
return false
},
// You would also need to intercept defineProperty, setPrototypeOf, etc.
})
return {
items: proxy,
add: (item) => {
items.push(item)
},
}
}
- Proxies require intercepting every mutating trap, and forgetting one introduces a hole.
- It’s more verbose and harder to reason about.
Object.freeze/unfreeze
clearly expresses intentional and scoped mutability.- Proxy traps have runtime overhead.
- The secret-based approach uses less memory, storing only a symbol, while proxies must keep all traps and the proxy object in memory.
- Iteration methods like
for...in
andObject.keys
may behave inconsistently depending on the proxy handler. - Proxies are not always fully transparent, which can cause unexpected behavior.
- From a developer experience standpoint, plain objects support full property introspection, autocompletion, and static analysis.
Proxies, on the other hand, obscure the object's shape — making tools like DevTools, autocomplete, and type systems less effective or unusable. - Both approaches rely on keeping something private — the
target
in a Proxy, or thesecret
inunfreeze
. But only thesecret
model enforces immutability at the object level, even if the reference leaks.
Open questions
- Should
Object.isSealed()
/Object.isFrozen()
report as sealed even when the object is reversible? - How would this affect internal engine mechanics?
- Would engines have any issues storing the
Symbol
as metadata? - Would it be better to introduce a new method, such as
Object.protect()
/Object.unprotect()
?
Feedback
I’d love to hear your thoughts on this direction. Especially:
- Viability concerns
- Implementation challenges (engine/runtime)
- Potential use cases I haven’t considered
- Interest from potential champions
If this idea resonates, I’d be happy to draft a full proposal repo and spec outline.
Thanks!