Search code examples
javascriptecmascript-6es6-classecmascript-next

Replacing values in prototype: class static getters, or class fields?


Years ago, I wrote my own "declare" implementation that mimicked Dojo's (anyone?): https://github.com/mercmobily/simpleDeclare This is now made obsolete by class. The beauty of SimpleDeclare is that I could do this:

// Basic definition of the managers store
var Managers = declare( JsonRestStores, JsonRestStores.HTTPMixin, JsonRestStores.SimpleDbLayerMixin, {

  schema: new Schema({
    name   : { type: 'string', trim: 60 },
    surname: { type: 'string', searchable: true, trim: 60 },
  }),

  storeName: 'managers',
  publicURL: '/managers/:id',

  handlePut: true,
  handlePost: true,
  handleGet: true,
  handleGetQuery: true,
  handleDelete: true,
});
var managers = new Managers();

Yes, it had multiple inheritance; and yes, things like handePut, handlePost, etc. were placed in a "middle-man" prototype. Having to convert this code to ES6, I Have two options: (Let's not cover "mixins" for now...)

OPTION 1: static getters (which will end up as Managers.***):

// Basic definition of the managers store
class Managers extends JsonRestStores {

  static get schema() { return new Schema({
    name   : { type: 'string', trim: 60 },
    surname: { type: 'string', searchable: true, trim: 60 },
  }),

  static get storeName() { return 'managers' }
  static get publicURL() { return '/managers/:id' }

  static get handlePut() { return true }
  static get handlePost() { return true }
  static get handleGet() { return true }
  static get handleGetQuery() { return true }
  static get handleDelete() { return true }
};
var managers = new Managers()

OPTION 2: class fields when they land (which will end up as normal object attributes when constructed):

// Basic definition of the managers store
class Managers extends JsonRestStores {

  schema = new Schema({
    name   : { type: 'string', trim: 60 },
    surname: { type: 'string', searchable: true, trim: 60 },
  })

  storeName = 'managers'
  publicURL = '/managers/:id'

  handlePut = true
  handlePost = true
  handleGet = true
  handleGetQuery = true
  handleDelete = true
};
var managers = new Managers()

The problem is that neither of these solutions really work as well as prototypes; having those values in the prototype gave 1) Defaults 2) The ability to an instance to find out what the parent's values were (e.g. manager.constructor.prototype). 3) The ability for an instance to change those values (this can be useful at times)

If I use static get, I get something that is quite verbose, with the upside that an instance can figure out the values of the parent, and parent's parent, is; and, unless I create setters for each one, I can't modify those values either

If I use class fields (which don't even exist yet), I get better syntax, with the major downside that an instance can't figure out what the earlier defaults were (since all values are in the instance)

What would you do?


Solution

  • Option 1 creates new Schema instance each time the property is accessed, which is inadmissible overhead, unless this is desirable behaviour.

    Option 2 creates new Schema instance for each Managers instance. If Managers is subclassed and schema is overridden, Schema will be instantiated any way, which provides overhead.

    If all Managers instances are supposed to share same schema instance, it should be assigned to class prototype:

    Managers.prototype.schema = new Schema(...);
    

    It can also be defined as static property and be optionally doubled with a getter to provide easier access on class instance.

    In ES.next:

    class Managers extends JsonRestStores {
      static schema = new Schema(...);
    
      get schema() {
        return this.constructor.schema;
      }
      ...
    }
    

    In ES6:

    class Managers extends JsonRestStores {      
      get schema() {
        return this.constructor.schema;
      }
      ...
    }
    
    Managers.schema = new Schema(...);