5

A pet peeve of mine in programming languages is taking the arguments passed in a constructor, and assigning them directly to fields in a class, named the same thing as the arguments I gave them in the constructor, like so:

class A {
    constructor(a, b) {
        this.a = a;
        this.b = b;
        etc...
    }
}

In order to get around this, I figured out I could create a function that took the existing keys in a class, and the arguments to them dynamically, like so:

function test(values) {
      Object.keys(this).map((x,i) => this[x] = values[i]);
}

class ChildObj {
      a1;
      a2;
      constructor() {
          test.call(this, arguments);
      }
}

This works perfectly, however, I would like to make it even more convenient by eliminating the repeated constructor declaration, as in my application I have many classes declared like this, and the same repeated 3 lines has begun to represent a significant chunk of the source code.

My first instinct was to use inheritance, however the fields are a property of the child object, and thus invisible to the parent. Then I tried to modify the class's constructor, which paired with an array of all the relevant classes could be done dynamically, however changing a class's constructor does not seem to be possible, as I tried

A.constructor = function() {
    test.call(this, arguments);
}

A.prototype.constructor = function() {
    test.call(this, arguments);
}

Temporarily, I pivoted to trying to define my object as a function, but that ran into the problem of accessing the names of the function parameters, and that's apparently only possible via Regexing the source code, which I'd rather avoid.

-----PS:-------

I have made somewhat of a breakthrough, managing to pass the child keys to the parent, by having the parent construct a copy of the child, but truncate the recursion there, like so:

class ParentObj {
    constructor() {
        if(ParentObj.constructor.in) {
            return;
        }
        ParentObj.constructor.in = true;
        let keys = Object.keys(new this.constructor);
        ParentObj.constructor.in = false;
        Object.keys(this).map((x,i) => this[x] = arguments[i]);
   }
}


class ChildObj extends ParentObj {
    a1
    a2
}

console.log(new ChildObj(3, 2));

However, it appears something else has broken, because it still does not assign the values to the resulting object.

-----END PS ---------

So, to conclude: Is it possible in Javascript to assign a constructor like this dynamically, and if not, is there another way of creating objects with trivial constructors like this? Thank you.

Vadi
  • 3,279
  • 2
  • 13
  • 30
James C
  • 143
  • 7
  • `A.prototype.constructor` is a self reference to `A`. With `new`, `A` gets called, not `A.prototype.constructor`. – Jonas Wilms Mar 07 '20 at 20:59
  • 1
    I'm interested in and more than willing answering anything related to this subject, if the provided code was just a little closer to a real use case and not that generic. Already passing a random number (as done with the example code) of arguments to a constructor function and just assigning each value without recognizable meaning to something public does shadow the core problem more than it does help to understand the real intention of choosing such a unique approach. – Peter Seliger Mar 07 '20 at 21:35
  • @peter My current specific use case involves the construction of an Abstract Syntax Tree for a toy compiler I'm writing. The classes here are representing the nodes in the tree, which are typed by the syntactic operation they represent. – James C Mar 07 '20 at 21:48
  • Provide e.g. the code of at least 3 of such classes as they would be written without any attempts of code reuse, Maybe also the code of how each type then gets constructed. Then one might see a pattern and might be able to come up with some approaches that tackle the code reuse issue. – Peter Seliger Mar 07 '20 at 22:00
  • @peter A typical node in an AST would look something like ```javascript class Program { constructor(statements){ this.statements = statements }} class AssignmentStatement{ constructor(id,expression){ this.id = id this.expressions = expression }} ``` The actual generator is called recursively, where the args in lowercase are parser objects from a concrete syntax tree: ``` program.ast = function(body){new Program(body.ast()) } assignment_statement.ast = function(id,_1,exp){new AssignmentStatement(id.ast(),exp.ast())} ``` – James C Mar 07 '20 at 22:22
  • your question is interesting as is, but can't you give an object to your constructor (rather than unnamed properties)? Assuming you have a grammar, you know the name of every "predicate" upon parsing, so you could simply instantiate ```new AssignmentStatement({id: id.ast(), exp: exp.ast()})``` ? Then you can trivially assign in the Parent constructor – grodzi Mar 08 '20 at 06:51

3 Answers3

2

No, this is not possible.

Those class fields are causing properties to get created in the constructor of the class, regardless whether it is an implicit one or whether you declared it explicitly. The parent constructor will never be able to access them, as you figured. Your best bet would be to use a decorator on the whole class, although for ES6 classes it's not exactly trivial.

The alternative would be to stop using class syntax, and just create your classes dynamically:

function makeClass(properties) {
    return function Class(...args) {
        for (const [i, p] of properties.entries())
            this[p] = args[i];
    };
}

const A = makeClass(["a1", "a2"]);
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • 1
    if I understand you correctly, are you saying that the implicit constructor is calling super, then assigning the values in the child class declaration to the declared values? That explains a lot. – James C Mar 07 '20 at 23:03
  • 1
    @JamesC Yes, exactly that's what happens – Bergi Mar 08 '20 at 12:15
0

I have found something that more or less works, though it requires a change to the calling of the constructor, which is less than ideal:

  class ChildObj2{
      a1
      a2
  }

  function ObjCreator(obj,...args){
      let o = new obj
      Object.keys(o).map((x,i) => o[x] = args[i])
      return o
  }

  console.log(ObjCreator(ChildObj2,3,2))
James C
  • 143
  • 7
  • 1
    You don't need to use `new obj.prototype.constructor`, the `obj` already is the constructor. Just do `new obj`. – Bergi Mar 07 '20 at 22:33
-1

If you remember class in js are just syntactic sugar for functions, this simplifies this considerably.

Create a classGenerator function that returns a function with the constructor you’ve defined, and voila, you’ve got a one liner to generate the initial classes.

Asher Gunsay
  • 259
  • 1
  • 5