I want to create an object with a hidden property(a property that does not show up in a for (var x in obj
loop). Is it possible to do this?
Here's a solution using the Proxy object.
an example event emitter:
class Event {
constructor(opts = {}) {
this.events = new Map
this.proxy = new class {}
Object.defineProperty(this.proxy, 'on', { value: this.on.bind(this) })
Object.defineProperty(this.proxy, 'emit', { value: this.emit.bind(this) })
Object.defineProperty(this.proxy, 'length', { get: () => this.length })
Object.defineProperty(this.proxy.constructor, 'name', {
value: this.constructor.name
})
return new Proxy(this.proxy, {})
}
on(topic, handler) {
if (!this.events.has(topic))
this.events.set(topic, new Set)
this.events.get(topic).add(handler)
return this.remove.bind(this, topic, handler)
}
emit(topic, ...payload) {
if (!this.events.has(topic))
return
const set = this.events.get(topic)
for (const fn of set)
fn(...payload)
}
remove(topic, handler) {
if (!this.events.has(topic))
return
const set = this.events.get(topic)
if (set.has(handler))
set.delete(handler)
}
get length() {
return this.events.size
}
}
Notice, in the constructor where it returns a new Proxy with a reference to the proxy property. I "decorated" the proxy object to make it look like the original class. You can get the length because I exposed that getter, but there's no way (as far as I know) to access the events Map directly and iterate over the keys. I guess this is sort of like a reverse closure? I'm not sure how this works in terms of garbage collection. But it does work to encapsulate functionality away from the user so they can't muck things up.
UPDATE:
So, that approach was interfering with prototypal inheritance. Here I figured out a similar but better technique by hooking into the construct method when the class is created and hoisting the "hidden" variable events
.
let Event =
class Event {
on(topic, handler) {
if (!events.has(topic))
events.set(topic, new Set)
events.get(topic).add(handler)
return this.remove.bind(this, topic, handler)
}
emit(topic, ...payload) {
if (!events.has(topic))
return
const set = events.get(topic)
for (const fn of set)
fn(...payload)
}
remove(topic, handler) {
if (!events.has(topic))
return
const set = events.get(topic)
if (typeof handler === 'undefined')
return events.delete(topic)
if (set.has(handler))
set.delete(handler)
}
get length() {
return events.size
}
}
let events
Event = new Proxy(Event, {
construct (target, args, self) {
events = new Map
return Reflect.construct(target, args, self)
}
})
Here's my gist for the more fully featured Event Emitter using this concept: https://gist.github.com/aronanda/18b6397c355da88ca170d01e0cc02628