4

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)

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
vinger
  • 43
  • 4
  • 2
    Please don't upload [images of code](https://meta.stackoverflow.com/a/285557/3082296). They can't be copied to reproduce the issue, they aren't searchable for future readers and they are harder to read than text. Please post the actual code **as text** to create a [mcve]. – adiga Nov 18 '22 at 10:37
  • JS assignment asigns the value. It’s not a pointer/reference – evolutionxbox Nov 18 '22 at 10:42
  • @evolutionxbox it is not a point of the question, i identify new value/reference/or what ever you want in child class as new value but it still have been used from parent if it still related to referenced values, can you pls explain it in details – vinger Nov 18 '22 at 10:46
  • https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get – evolutionxbox Nov 18 '22 at 14:13
  • Why are you putting the answer in the question? – evolutionxbox Nov 19 '22 at 14:48

3 Answers3

2

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()
t.niese
  • 39,256
  • 9
  • 74
  • 101
2

In your first example, the creation and initialization of the public instance field named defaultValue in Child occurs after the creation and initialization of the public instance field named value in Parent.

So: even though the this value in the initializer of the public instance field named value in Parent will point to the instance of Child under construction, the child-local public instance field named defaultValue does not yet exist, and so the prototype chain is followed up to the property named defaultValue on the instance of Parent, yielding 1.

In your latter example, you have getter functions named defaultValue.

Getter functions specified in this way, even though their API deliberately looks like that of public instance fields, will end-up as functions on the [[Prototype]] of any instance under construction.

The [[Prototype]] objects of instances are created at the time of class declaration (ie. always before anything triggered by instance construction), as the .prototype property of the class (or constructor function); references to these objects are then copied to the [[Prototype]] of any instance under construction as the first step in object construction (think Object.create(class.prototype)).

And so this.defaultValue from the Parent public instance initializer for value resolves to the getter on the [[Prototype]] of the instance of Child under construction, which is a function that returns 2.

Ben Aston
  • 53,718
  • 65
  • 205
  • 331
1

It is happening because getters are defined on prototypes while instance properties are defined on instance (as the name imply)

So, when Child1 instance is created it first defines properties from Parent1 and you get defaultValue = 1

On contrary when Child2 instance is created the Child2.prototype will already have property defaultValue overriden.

class Parent1 {
  defaultValue = 1;
  value = this.defaultValue;
}

class Child1 extends Parent1 {
  defaultValue = 2;
}




class Parent2 {
  get defaultValue() { return 1; }
  value = this.defaultValue;
}


class Child2 extends Parent2 {
  get defaultValue() { return 2; }
}


console.log(Object.hasOwn(new Child1(), 'defaultValue'))
console.log(Object.hasOwn(new Child2(), 'defaultValue'))
Yury Tarabanko
  • 44,270
  • 9
  • 84
  • 98
  • It is still a bit strange (from the perspective of other languages) that the `parent` has access to the `child` functions in the _initialization_ phase. So I absolutely can understand that this behavior is not expected. – t.niese Nov 18 '22 at 15:29
  • @t.niese I guess It is all happening because there are no classes in js. Just a syntactic sugar for prototypes. And unfortunately js developer have to keep it in mind. For example `class C{ [Math.random()] = Math.random(); }` is a bit strange because prop name is not random :) – Yury Tarabanko Nov 18 '22 at 16:03