5

Consider the following:

class Base {
   _value;
   constructor() {
      this._value = 1;
   }
   get value()  { return this._value; }
   set value(v) { this._value = v;    }
}
class Derived extends Base {
   set value(v) {
      // ...
      super.value = v;
   }
}
const d = new Derived();
d.value = 2;
console.log(d.value); // <-- undefined

I expected the "getter" method of the Base class to be "inherited" in the class of Derived, and consequently to display the value of 2 instead of undefined. Seems like both of the "getter" or "setter" methods are not inherited independently, but rather are considered as a single entity, collectively. In a sense that if the overrided setter method was not present or it was coupled with a respective getter (specifically declared in the derived class, rather than being inherited) as the following:

get value() { return super.value; }

then, there would be no such issue.

So, why the getters or the setters are not inherited independently, as they're supposed to decouple the concepts of reading and setting the fields?

  • 1
    "*as they're supposed to decouple the concepts*" - which concepts? – Bergi Mar 30 '20 at 18:07
  • 2
    @Bergi The concept of _reading_ and _setting_ the field. –  Mar 30 '20 at 18:09
  • Possible duplicate of [Override a setter, and the getter must also be overridden](https://stackoverflow.com/q/28950760/1048572) – Bergi Mar 30 '20 at 18:10
  • 1
    I see pretty high coupling between those. In any case, the answer you're looking for is probably "Because the property is inherited as a whole". – Bergi Mar 30 '20 at 18:11
  • @goodUser Javascript class inheritance is based on prototype and it isn't working like classic OOP. Check my updated answer. – Christos Lytras Mar 30 '20 at 18:44
  • I don't understand why people complain JavaScript isn't behaving like normal OOP. JavaScript wasn't created as OOP in the first place. Use JavaScript as it is instead trying to force it to behave like Java. – Pushkin Apr 06 '20 at 01:57
  • It's impossible to answer this question. It like asking "Why is JavaScript object-oriented?". Why? Becuase that's just how the language was designed. – D. Pardal Apr 06 '20 at 10:36

5 Answers5

2

JavaScript’s class inheritance uses the prototype chain to wire the child Constructor.prototype to the parent Constructor.prototype for delegation. Usually, the super() constructor is also called. Those steps form single-ancestor parent/child hierarchies and create the tightest coupling available in OO design.

I suggest you read a very nice article about Master the JavaScript Interview: What’s the Difference Between Class & Prototypal Inheritance? written by Eric Elliott.

UPDATE

For elaboration; this is the expected behavior since you're adding a new descriptor to Derived.prototype. When you add a descriptor using get or set, there is actually a function created with that name so it will evaluate to undefined if is not set. It becomes like an own property.

Standard ECMA-262

14.3.9 Runtime Semantics: PropertyDefinitionEvaluation

  1. MethodDefinition : set PropertyName ( PropertySetParameterList ) { FunctionBody }
  2. Let propKey be the result of evaluating PropertyName. ReturnIfAbrupt(propKey).
  3. If the function code for this MethodDefinition is strict mode code, let strict be true. Otherwise let strict be false.
  4. Let scope be the running execution context’s LexicalEnvironment.
  5. Let formalParameterList be the production FormalParameters : [empty]
  6. Let closure be FunctionCreate(Method, PropertySetParameterList, FunctionBody, scope, strict).
  7. Perform MakeMethod(closure, object).
  8. Perform SetFunctionName(closure, propKey, "set").
  9. Let desc be the PropertyDescriptor{[[Set]]: closure, [[Enumerable]]: enumerable, [[Configurable]]: true}
  10. Return DefinePropertyOrThrow(object, propKey, desc).

6.2.4.6 CompletePropertyDescriptor ( Desc )

When the abstract operation CompletePropertyDescriptor is called with Property Descriptor Desc the following steps are taken:

  1. ReturnIfAbrupt(Desc).
  2. Assert: Desc is a Property Descriptor
  3. Let like be Record{[[Value]]: undefined, [[Writable]]: false, [[Get]]: undefined, [[Set]]: undefined, [[Enumerable]]: false, [[Configurable]]: false}.
  4. If either IsGenericDescriptor(Desc) or IsDataDescriptor(Desc) is true, then
    • a. If Desc does not have a [[Value]] field, set Desc.[[Value]] to like.[[Value]].
    • b. If Desc does not have a [[Writable]] field, set Desc.[[Writable]] to like.[[Writable]].
  5. Else,
    • a. If Desc does not have a [[Get]] field, set Desc.[[Get]] to like.[[Get]].
    • b. If Desc does not have a [[Set]] field, set Desc.[[Set]] to like.[[Set]].
  6. If Desc does not have an [[Enumerable]] field, set Desc.[[Enumerable]] to like.[[Enumerable]].
  7. If Desc does not have a [[Configurable]] field, set Desc.[[Configurable]] to like.[[Configurable]].
  8. Return Desc.

Also have a look at 6.1.7.2 Object Internal Methods and Internal Slots at Table 5 - Essential Internal Methods and especially GetOwnProperty and DefineOwnProperty.

[[GetOwnProperty]] (propertyKey) → Undefined | Property Descriptor

Return a Property Descriptor for the own property of this object whose key is propertyKey, or undefined if no such property exists.

[[DefineOwnProperty]] (propertyKey, PropertyDescriptor) → Boolean

Create or alter the own property, whose key is propertyKey, to have the state described by PropertyDescriptor. Return true if that property was successfully created/updated or false if the property could not be created or updated.

Christos Lytras
  • 36,310
  • 4
  • 80
  • 113
  • I'm aware of the behavior, and your suggestion is already in the original post! The concern is about "why" is this that?! –  Mar 30 '20 at 18:30
  • 3
    Thanks for the updates, could you please elaborate on the only concern of: why the _getters_ or the the _setters_ are not (or, should not be) inherited independently. –  Mar 30 '20 at 18:54
  • @goodUser it's expected behavior because when you define a setter or getter descriptor, the name is reserved to that class because it actually create a prototype function under the hood. Please check my updated answer, I have included the standard ECMA specs. – Christos Lytras Mar 30 '20 at 23:56
-1

Acessor descriptors have a getter and a setter. You can't have getters/setters without acessor descriptors. Derived.prototype contains the acessor descriptor for value that you specified in Derived. That's just how JavaScript works.

If you want to inherit the getter, you could change the prototype of Derived manually, like so:

Object.defineProperty(Derived.prototype, "value", {
    get: Object.getOwnPropertyDescriptor(Base.prototype, "value").get,
    set: Object.getOwnPropertyDescriptor(Derived.prototype, "value").set
});
D. Pardal
  • 6,173
  • 1
  • 17
  • 37
  • 2
    _That's just how JavaScript works_: that's the only relevant part to the concern of "why is that"! However, your approach is very JavaScript-ish! –  Mar 30 '20 at 18:40
  • What do you mean by very JavaScript-ish? – Pushkin Apr 06 '20 at 01:59
-1

The problem that I see is the use of super. if you instead use super() in the constructor for the child class you will get all the method from the parent, so you don't need a setter for value in the child cuz is inherited

class Base {
   _value;
   constructor() {
      this._value = 1;
   }
   get value()  { return this._value; }
   set value(v) { console.log('value in parent',v), this._value = v;    }
}
class Derived extends Base {
   constructor() {
      super();
  }
}
const d = new Derived();
d.value = 7;
console.log(d.value); // <-- 7
  • 1
    As I've stated in the original post; _"if the overrided setter method was not present.., then, there would be no such issue"_; furthermore, the _overrided setter_ is about to do some custom processing; even further, as per the original post; I know how to resolve the issue, while my concern is about: _"why is that"!_ After all, [Christos](https://stackoverflow.com/users/1889685/christos-lytras) has kindly provided [interesting points](https://stackoverflow.com/a/60936882/5222086). –  Apr 03 '20 at 04:05
  • 1
    ... finally, the issue is not related to the lack of the child-class-constructor, as per the [specification](https://tc39.github.io/ecma262/#sec-runtime-semantics-classdefinitionevaluation), such a construct will be generated implicitly when it's not present. Also, as per the original post, such a behavior is not observed when both the child-class-constructor and the setter are not present altogether! –  Apr 03 '20 at 04:27
-1

public class Car extends Vehicle {

private int numDoors;
private int numWheels;


public Car(String manufacturer,String model,int maxSpeed,double price,int numWheels
        ,int numDoors)
{
    super(manufacturer,model,maxSpeed,price);
    this.numDoors=numDoors;
    this.numWheels=numWheels;

}

public Car()
{

}


public int getNumDoors()
{
    return numDoors;
}


public void setNumDoors(int numDoors)
{
    this.numDoors = numDoors;
}


public int getNumWheels()
{
    return numWheels;
}


public void setNumWheels(int numWheels)
{
    this.numWheels = numWheels;
}

public String toString()
{
    return ("Number of doors:"+numDoors+"\n"+"Number of wheels:"+numWheels+""
            + "\n"+
    "Manufacturer:"+manufacturer+"\n"+
           "Model:"+model+"\n"+"Maximum Speed:"+maxSpeed+"\n"+"Price in euros:"+price+
           "\n");
}

}

package testvehicle;

public class MotorCycle extends Vehicle { private String seat;

public MotorCycle(String manufacturer,String model,int maxSpeed,double price
        ,String seat)
{
    super( manufacturer, model, maxSpeed, price);
    this.seat=seat;
}


public MotorCycle()
{

}


public String getSeat()
{
    return seat;
}


public void setSeat(String seat)
{
    this.seat = seat;
}


public String toString()
{
    return ("Manufacturer:"+manufacturer+"\n"+
           "Model:"+model+"\n"+"Maximum Speed:"+maxSpeed+"\n"+"Price in euros:"+price+
           "\n"+"Seat type:"+seat+"\n");
}

}

package testvehicle;

public abstract class Vehicle//This class doesn't do something!
{
    protected String manufacturer;
    protected String model;
    protected int maxSpeed;
    protected double price;

    public Vehicle(String manufacturer,String model,int maxSpeed,double price)
    {
        this.manufacturer=manufacturer;
        this.model=model;
        this.maxSpeed=maxSpeed;
        this.price=price;

    }

    public Vehicle()
    {

    }


    public String getManufacturer()
    {
        return manufacturer;
    }


    public void setManufacturer(String manufacturer)
    {
        this.manufacturer = manufacturer;
    }


    public String getModel()
    {
        return model;
    }


    public void setModel(String model)
    {
        this.model = model;
    }


    public int getMaxSpeed()
    {
        return maxSpeed;
    }


    public void setMaxSpeed(int maxSpeed)
    {
        this.maxSpeed = maxSpeed;
    }


    public double getPrice()
    {
        return price;
    }


    public void setPrice(double price)
    {
        this.price = price;
    }



   public String toString()
    {
       return ("Manufacturer:"+manufacturer+"\n"+
               "Model:"+model+"\n"+"Maximum Speed:"+maxSpeed+"\n"+"Price in euros:"+price+
               "\n");
    }



}

package testvehicle;

public class Main
{


    public static void main(String[] args)
    {
        Car C=new Car("Opel","Corsa",220,12000.0,4,5);
        MotorCycle M=new MotorCycle("KTM","DUKE-690",250,9000.0,"Agressive");
        System.out.println(C.toString());
        System.out.println();
        System.out.println(M.toString());

    }


}
  • This **does not have anything to do with the question** so please consider deleting this. Even if it does, its in a different language as the question – I_love_vegetables Jul 22 '21 at 05:47
-2

I'd like to stress out what others already said: that's the way Javascript works.

Javascript was forged in browsers' wars. It's a chaotic set of features, selected with compromises and backward compatibility in mind, not a carefully designed language.

So asking "why getters and setters work the way they do?" is almost like asking why:

console.log(
  (![]+[])[+[]]+(![]+[])[+!+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]
)
// === fail

or, if we're talking about OOP, "why this works in JS the way it does?"

That should be enough to answer the question "why?".

But also consider:

  1. How to enumerate setters and getters if you want them to be similar to ordinary functions, which are (normally) enumerable in JS? Especially considering that setter and getter have the same name?

    function A() {}
    A.prototype.f = function() {}
    const obj = new A()
    for(const key in obj) console.log(key, "is own property:", obj.hasOwnProperty(key))
  2. Some folks even say that the whole idea of inheritance is evil. And in the case of getters and setters I, personally, think that this code:

    class B extends A { get x() { ... } }
    

    reads as "class B has a read-only property x" and an explicit setter (should it present) would make this code cleaner.

  3. The OOP itself is not carved in stone. Every language implements it in it's own way.

  4. MDN

    JavaScript classes, introduced in ECMAScript 2015, are primarily syntactical sugar over JavaScript's existing prototype-based inheritance. The class syntax does not introduce a new object-oriented inheritance model to JavaScript.

    And getters/setters existed long before that.

  5. The prototype inheritance back in the days was considered by some as a paradigm in its own right.

  6. https://esdiscuss.org/topic/getter-and-setter-inheritance

    My view is that getters and setters introduce properties and that whatever we do should be appropriate to that model

    I do not really understand what they mean by "that model", probably they neither. But that's their opinions. Correct me if I'm wrong... opinions of people influenced the JS history (???). And I might've missed it, but I saw no articulated reasons they are based on.

x00
  • 13,643
  • 3
  • 16
  • 40