Is it possible to create a hidden property in javascript

前端 未结 6 2044
刺人心
刺人心 2020-12-08 04:59

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?

6条回答
  •  挽巷
    挽巷 (楼主)
    2020-12-08 05:32

    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

提交回复
热议问题