-1

I'm trying to do some parent/child style inheritence in JavaScript and running into some unexpected behavior:

class Dad {

  constructor() {

    this.init();

  }

  init() {}

}

class Son extends Dad {

  foo;

  init() {

    this.foo = 'bar';

    console.log('init', this.foo);

  }

}

let son = new Son();

console.log('son', son.foo);

Expected output:

init bar
son bar

Actual output:

init bar
son undefined

Why is son.bar getting unset between son.init() being called and the parent constructor returning the new object?

Dan
  • 744
  • 1
  • 8
  • 23
  • 1
    I see `son bar` when I run this. – user2357112 Oct 03 '22 at 02:07
  • Confirmed... I can't figure out why this is happening in my actual code. Guess I'll close for now and re-open when I can replicate. – Dan Oct 03 '22 at 02:09
  • Updated. Adding `foo;` to the `Son` class definition breaks it. – Dan Oct 03 '22 at 02:22
  • Why are you using an `init` method? Put `this.foo = 'bar'` in the `constructor` of `Son` and it'll work – Bergi Oct 03 '22 at 03:50
  • The parent is defining function calls in the constructor that need to be implemented by the children. That way each time I add a new child type I know I need to implement the functions called by the parent. – Dan Oct 03 '22 at 04:32
  • 1
    Well [don't call overridden methods from the constructor](https://stackoverflow.com/a/47821096/1048572) – Bergi Oct 03 '22 at 04:38
  • I thought that might be the case... hence my not-so-elegant `callMeAfterSuper()` solution below. – Dan Oct 03 '22 at 04:43
  • That still doesn't work for a grandson. You'll need a `callMeAfterNew` method. – Bergi Oct 03 '22 at 04:55
  • I see... is there an established code pattern that solves this? – Dan Oct 03 '22 at 05:02

2 Answers2

0

So apparently this is an order-of-operations issue.

Execution order:

  1. Son implied constructor is called
  2. ... which calls super() or Dad constructor
  3. Son.init() is called
  4. this.foo is set to 'bar' (Dad constructor finished)
  5. After super() constructor is finished, the child Son class apparently sets properties defined in the Son class scope to their defaults, in this case foo; or foo = undefined;

I don't like it, but one solution is to remove foo; from the Son class definition. Apparently VS Code intellisense will still pick up on the foo property even if it's not defined in the class definition or constructor, which is one of the goals. As far as readability goes, it's slightly worse not to have foo in the class definition or constructor.

Another solution:

class Dad {

  // To be called after super() in child constructors
  //    Class field initializers overwrite assignments made by super(), so
  //    anything set here could be overwritten if it were in the constructor
  callMeAfterSuper() {

    this.init();

  }

  init() {}

}

class Son extends Dad {

  foo;

  constructor() {

    super();
    super.callMeAfterSuper();

  }

  init() {

    this.foo = 'bar';

    console.log('init', this.foo);

  }

}

let son = new Son();

console.log('son', son.foo);
Dan
  • 744
  • 1
  • 8
  • 23
  • 1
    Related: https://stackoverflow.com/questions/64357900/javascript-why-does-declaring-a-property-in-a-subclass-overwrite-the-same-prope – Dan Oct 03 '22 at 02:39
0

Addressing JavaScript subclass method called by parent constructor transiently setting instance properties:

Another approach compatible with grandchildren but even less elegant:

    class Dad {

        // To be called after super() in child constructors
        //    Class field initializers overwrite assignments made by super(), so
        //    anything set here could be overwritten if it were in the constructor
        callMeAfterSuper() {

            this.init();

        }

        init() { }

    }

    class Son extends Dad {

        foo;

        constructor() {

            super();
            if( this.constructor.name === Son.name ) super.callMeAfterSuper();

        }

        init() {

            super.init();

            this.foo = 'bar';

        }

    }

    class Grandson extends Son {

        littleFoo;

        constructor() {

            super();
            if( this.constructor.name === Grandson.name ) super.callMeAfterSuper();

        }

        init() {

            super.init();

            this.littleFoo = 'littleBar';

        }

    }

    let son = new Grandson();

    console.log('son', son.foo, son.littleFoo);
Dan
  • 744
  • 1
  • 8
  • 23