Search code examples
javascriptextends

Extends behaviour in js with default value


I have found interesting behaviour of js extends, and do not understand the reasons of it
in case of copy values right from another value, for some reasons value will be copied from parent

class parent {
  defaultValue = 1;
  value = this.defaultValue;
}
new parent() // {defaultValue: 1, value: 1}
class child extends parent {
  defaultValue = 2;
}
new child() // {defaultValue: 2, value: 1}

which is really not obvious and unclear for me
but if i replace it by function or even getter the behaviour will be changed, and i get the value from child

class parent {
  get defaultValue() { return 1; }
  value = this.defaultValue;
}
new parent() // {defaultValue: 1, value: 1}
class child extends parent {
  get defaultValue() { return 2; }
}
new child() // {defaultValue: 2, value: 2}

the main question here, is why in the moment of child creation in first case JS looking on parent class to take value, but in second case JS looking on child class to take value

Can someone explain the reason of such behaviour?

EDIT See t.niese or Yury Tarabanko answers for details

the short answer seems in next way

getters(also function) and function will be overridden in prototype which allow them to be called by parent with child changes (in real it is expected)

While first example with assignee simple values will be called only in the moment of class creation (constructor or super) and it will be appear only in scope of current class (which cannot be changed by child) and prototype (which can be changed by child)


Solution

  • A related question is: how to access overridden parent class functions in parent class code.

    Getters and Setters are functions that are defined with the definition of the class, so in the constructor of the parent class (and the initiation of its instance class fields) you could call a function that only exists in child (which indeed might be a bit strange):

    class parent {
      value = this.test();
      constructor() {
        this.test()
      }
    }
    
    class child extends parent {
      test() {
        console.log('test')
      }
    }
    
    new child()

    So which function (or getter/setter) is called is already defined with the class definition, before the instancing is done.

    Public instance class fields on the other hand are initialized/set during initialization phase of an instance in an particular order (the shown code might only work in chrome based browsers):

    class parent {
      defaultValue = (() => {
        console.log('parent:init defaultValue')
        return 1;
      })();
    
      value = (() => {
        console.log('parent:init value')
        return this.defaultValue;
      })();
    
      constructor() {
        console.log('parent constructor')
      }
    }
    
    class child extends parent {
      defaultValue = (() => {
        console.log('child:init defaultValue')
        return 2;
      })();
    
    
      constructor() {
        console.log('child constructor before super()')
        super()
        console.log('child constructor after super()')
      }
    }
    
    new child()