-1

I got a class Parent, and a class Child extending Parent. In Parent's constructor I need now to determine if x is an instance of the original class or of the child class.

As in my case Parent is actually anonymous I'm using instanceof of this.constructor which isn't working as expected:

class Parent {
  constructor(x) {
    if (x instanceof this.constructor) {
      console.log("x is instanceof of this.constructor");
    }
    if (x instanceof Parent) {
      console.log("x is instanceof of Parent");
    }
  }
}

class Child extends Parent {
  constructor(x) {
    super(x);
  }
  foo(x) {
    new this.constructor(new super.constructor(x))
  }
}

Calling

new Child().foo()

now yields x is instanceof of Parent but not x is instanceof of this.constructor. What am I doing wrong and how can I fix this without using x is instanceof of *Parent*?


The reason for all this is that I'm wrapping my class definition so that I can create a new instance without new. The class is assigned to Parent after wrapping/mutating, so the class that is wrapped should be anonymous.

I don't know if that makes any sense, and my reduced example may fail to make clear what's the point of all this, but apart from the one issue explained above it's actually quite handy:

X()   // create new instance of Parent without new
X.f   // access static methods
const { X, c: Parent } = function (c) {
    return {
        X: Object.assign(
            function () {
                return new c(...arguments);
            },
            _.pick(c, Object.getOwnPropertyNames(c)  // using lodash here
                .filter(n => typeof c[n] === "function")
            )
        ),
        c
    };
}(class {
  constructor(x) {
    if (x instanceof this.constructor) {
      console.log("x is instanceof of this.constructor");
    }
    if (x instanceof Parent) {
      console.log("x is instanceof of Parent");
    }
  }
})

class Child extends Parent {
  constructor(x) {
    super(x);
  }
  foo(x) {
    new this.constructor(new super.constructor(x))
  }
}
DonFuchs
  • 371
  • 2
  • 14
  • I think `this.constructor` is `Child` not `Parent`. --- Also, `super.constructor` isn't used in the example – evolutionxbox Jan 12 '21 at 22:00
  • "*in my case Parent is actually anonymous*" - just fix that problem so that you can write `instanceof Parent`, which is what you want and would work. – Bergi Jan 12 '21 at 22:04
  • @evolutionxbox Hm yes, got something wrong there; should be fine now – DonFuchs Jan 12 '21 at 22:06
  • 3
    Please show us your actual code with the anonymous class and why you need that to solve your actual problem. Given your previous question, it seems this approach causes nothing but problems. – Bergi Jan 12 '21 at 22:07
  • This sounds like really questionable design. What is the real-world situation that led to this? – Ry- Jan 12 '21 at 22:07
  • @Bergi I was expecting this... I finally added some more code – DonFuchs Jan 12 '21 at 22:26
  • Please don't blame me if you think it's rubbish – DonFuchs Jan 12 '21 at 22:29
  • I really recommend just using `new`. – Ry- Jan 12 '21 at 22:34
  • But I explicitly need to get an instance of Parent so I need to use super, and `new super` is not working – DonFuchs Jan 12 '21 at 22:43
  • 1
    So `instanceof Parent` would work. But you ideally would want to write `instanceof X` if I understood you correctly? Of course, even currently nothing prohibits you from writing `const X = (c => { … })(class local { constructor(p) { if (p instanceof local) console.log("got Parent/X instance"); } });` – Bergi Jan 12 '21 at 22:55
  • 1
    "*I'm wrapping my class definition so that I can create a new instance without new.*" - that's a pretty basic wrapping, and I don't understand why you only copy static methods. You'll want to have a look at [the tricks shown/mentioned in this answer](https://stackoverflow.com/a/31789308/1048572). – Bergi Jan 12 '21 at 22:59
  • @Bergi Thanks. I think I will finally stick to the [local] approach; in the end it's acutally shorter and more concise, and it solves my problem – DonFuchs Jan 13 '21 at 08:21
  • @Bergi And thanks for the reference, I think I will also refine my wrapping – DonFuchs Jan 13 '21 at 08:23

1 Answers1

1

Firstly the bindings of Parent and Child identifiers are the Parent and Child class constructor functions and constant. When used as the first parameter of Object.assign, the Parent binding has not been created but while methods are being assigned to function c, the function is not actually called. So when it comes to calling class constructor code later, both Parent and Child variables refer to the appropriate class constructor.

Hence

class {
  constructor(x) {
    if (x instanceof Parent) {
      console.log("x is instanceof of Parent");
      if( x instance of Child) {
      console.log("x is instance of Child");
    }
  }
}

should indicate if the parameter is an instance of Parent or Child. Of course because the class extension puts Parent.prototype at the head of Child.prototype's inheritance chain, every instance of Child it is also an instance of Parent.

The results obtained from code examining instanceof values are as to be expected:

  1. foo creates an [Object Parent] object parameter to pass to the Child constructor

  2. Child calls the super constructor with the supplied parameter taking the form:

      super([Parent Object])
    

    but with the this value seen by super set to the new Child object instance.

  3. In the super constructor the [Object Parent] x argument is not an instance of this.constructor, which is Child, so no message saying so is generated.

You may wish to see if the full code could be simplified by using less complexity, along the structural lines of:

 class Parent {..........}
 class Child extends Parent {.....}
 const X = Object.assign(.......)
traktor
  • 17,588
  • 4
  • 32
  • 53
  • Thanks, that made it clear to me. I understand that I should not do this as a one liner, the only (pedantic) concern I have is that I do think of X and Parent as of the same thing (and Parent is actually only stored because it's needed for Child definition) and struggle in naming them seperately. – DonFuchs Jan 13 '21 at 08:16
  • My point is, I initially designed the wrapping function to not even return the Parent definition but to hide it completely; but I guess even then it doesn't hurt to locally assign a name, like [local] suggested above. – DonFuchs Jan 13 '21 at 08:19