ES6 Static Properties Subtle (Mis)Behavior in Inheritance - javascript

When one defines a static property on an ES6 class, and you have other classes that extend that class, any change to the property on the parent class will seem to change the value on all subclasses. This seems to be a subtle prototype chain issue. If you first set the property value on the subclass then it seems to "own" it--i.e. it won't reflect changes to the super class. My issue is that I want a class property that flags a guaranteed one-time-only initialization the first time a class is used, and because this is a library/framework I don't want to have to force users to explicitly set the static value in their own subclass definitions. Other than having a global that tracks it instead, is there any other way to keep static properties separate across subclasses?
Code that demonstrates problem:
class Class1 {
static get initialized() { return this.$initialized }
static set initialized(flag) { this.$initialized = flag}
}
class Class2 extends Class1 {}
Class1.initialized -> undefined
Class1.initialized = false;
Class2.initialized -> false
Class1.initialized = true;
Class2.initialized -> true

The problem is that the $initialized property is inherited as well, so once the superclass is initialised then this will be reflected in all subclasses. You can write your getter so that it ignores inherited properties though:
class Class1 {
static get initialized() {
return this.hasOwnProperty("$initialized")
? this.$initialized
: undefined; // or whatever default value you want
}
static set initialized(flag) {
this.$initialized = flag
}
}

Related

cannot understand the Javascript setter and getter lookup in classes

I understand that JavaScript classes are syntactic sugar over prototypes and not works as pure classes in other OOP languages. But the problem here arises is on the getter and setter. When completely leaving getter and setter in the child class, it behaves normally but if I define any one of getter or setter broke these.
For example:
class A {
constructor() {
this.__value = "Default";
}
set value(value) {
console.log("setter called")
this.__value = value;
}
get value() {
console.log("getter called")
return this.__value;
}
}
class B extends A {
}
let b = new B();
b.value = 2;
console.log(b.value);
The above code works fine but if I define getter or setter in the child class the lookup won't go to parent class. I googled about it and found that, it is intentional and is ES standard. Then why when the child class has no methods the lookup reaches the parent class and able to access the getter and setter?
When you access an object property, either to set a value obj.prop = 'val' or to retrieve a value console.log(obj.prop), the engine will try to find that property in the object's prototype chain.
It starts at the instance. In this case, the instance doesn't have any properties, so it continues.
The next object is the instance's internal prototype, which is B.prototype. If B has the property name being looked at, it'll stop there. If there's a setter, and a value was assigned to the property, the setter will be invoked. If there's a getter, and the value being retrieved, the getter will be invoked.
That's it - once a property is found in the prototype chain, it'll stop searching.
If B.prototype didn't have the setter/getter, then the engine would proceed to the next object. The internal prototype of B.prototype is A.prototype. Then, A's setter or getter would be invoked.
It's possible to invoke the superclass's setters/getters from the child, but it looks a bit strange:
class A {
constructor() {
this.__value = "Default";
}
set value(value) {
console.log("setter called")
this.__value = value;
}
get value() {
console.log("getter called")
return this.__value;
}
}
class B extends A {
set value(value) {
console.log('sub setter calling super');
super.value = value;
}
get value() {
console.log('sub getter calling super');
return super.value;
}
}
let b = new B();
b.value = 2;
console.log(b.value);
After googling and lot of code execution I found an answer to my own question,If we define no accessors(getter or setter) in our child class the lookup will proceed towards the parent but if we set any accessor using the get or set descriptor in child class that will invoke the Object.defineProperty method and set the descriptor we mentioned.If we have both setter and getter there is no problem,but if we leave any one that leads to missing of those property in our current object.That's the reason why we get undefined as the undefined property gets undefined value.So literally we have found the value which is undefined thats the main reason we cannot proceed to the parent prototype as value(undefined) found in the current object and the lookup will stop.

What exactly does a derived class inherit from its base class?

I currently am learning ES6 classes for Javascript and I seem to understand the concept of them but I don't understand what a derived class inherits from its base class. Is it the methods of the class? Is it safe to assume that the methods of a class are properties of said class? In which case, they are part of the prototype and are thus inherited by objects down the prototype chain. And what about the constructor? Are properties defined inside the constructor inherited?
Thank you for your consideration!
Classes are more or less just syntactic sugar for setting up prototype inheritance.
class Derived extends Base {}
is equivalent to
function Derived(...args) {
return Base.apply(this, args);
}
Object.setPrototypeOf(Derived, Base);
Object.setPrototypeOf(Derived.prototype, Base.prototype);
There is some "magic" involved with super and in the future with public and private class fields, but the basic relationship between objects is the same.
Is it safe to assume that the methods of a class are properties of said class? In which case, they are part of the prototype and are thus inherited by objects down the prototype chain.
Yes, methods become properties of the corresponding prototype object, from which all instances inherit. I.e.
class Foo {
bar() {}
}
is equivalent to
function Foo() {}
Foo.prototype.bar = function() {}
And since a "base class'" property object is in the prototype chain of the derived class, all its methods are available to instances of the derived class.
Are properties defined inside the constructor inherited?
"Inherited" is the wrong word here. The properties are created on the instance itself since that's how constructors work.
Consider the process to be like this:
// Create new instance
var newInstance = Object.create(Derived.prototype);
// Call base constructor with `this` set to new instance
Base.apply(newInstance);
// Call derived constructor with `this` set to new instance
Derived.apply(newInstance);
If the base constructor contained something like this.base = 42;, then that property would be directly created on the new instance, since this refers to the new instance.
Note: In reality the exact flow is a bit different due to the fact extending built-in classes such as Array need special treatment but the end result is roughly the same.
You didn't ask about static methods but these are still part of the inheritance. static methods become properties of the constructor function itself.
class Foo {
static bar() {}
}
is equivalent to
function Foo() {}
Foo.bar = function() {}
Because the constructor of the base class becomes the prototype of the constructor of the derived class, all properties defined on the base constructor are available to the derived constructor.
The developer tools in your browser can actually show you all of this:
class Base {
static staticBase() {}
constructor() {
this.base = 42;
}
fromBase() {}
}
class Derived extends Base {
static staticDervied() {}
constructor() {
super(); // necessary since we extend base
this.derived = 21;
}
fromDerived() {}
}
console.dir(Derived);
console.dir(new Derived());
there are a lot of great resource about fundamental es06
example - https://exploringjs.com/es6/ch_classes.html#details-of-subclassing

this is not allowed before superclass constructor invocation

I want to pass child class instance to super class using super's constructor but I'm get this error
super(this);
this is not allowed before superclass constructor invocation
Why i'm getting this error , also how could I resolve this issue
class Parent
{
constructor(child)
{
this.child = child;
}
//...somewhere in code
//child.doSomething();
}
class Child extends Parent
{
constructor()
{
super(this); // <==== the error here
}
doSomething = () =>
{
//...
}
}
There's no need to pass this to super() because this inside the superclass constructor will be a reference to the same object. Recall that your class hierarchy will cooperate to perform initialization on a single new object.
Calls to super() must come before any reference to this, including in the super() argument list. Why? Because in order to mimic behavior of other OO languages, it must be the case that the top-most initializer in the class hierarchy gets its hands on the new object first. The parent (or "senior") initializer should be able to assume that prototype methods at that level have the semantics the base class expects, since it doesn't "know" what subclasses might have done with their prototypes etc. If a subclass initializer could modify the new object and override a base class prototype method (or something else of that flavor), it'd be chaos.
In the child's constructor method, the parent's constructor must be called before accessing this. The superclass' constructor will have access to this anyway, since you are constructing an instance of the superclass - though it will not have access to any other initialization that your child constructor may yet do.

JavaScript: How to get child Class's methods in parent's constructor?

How to get (console.log for ex.) B class's methods in A class's constructor?
class A {
constructor() {
// GET B's method names ('ok', ...) here
}
}
class B extends A {
constructor() {
super();
}
ok() {
}
}
In the "base" constructor you have access to complete object, so can check what is its real constructor and so its prototype const childClassPrototype = this.constructor.prototype. Having a "child" prototype you can get a list of its properties with Object.getOwnPropertyNames(childClassPrototype). From that list you want to filter out "constructor" and properties that are not functions.
Note: this technique will only give you access to "leaf" prototype, once you may have a multi level prototype chain. Thus you have to iterate over prototype chain.
Note2: for autobinding you may like to consider using a decorator. One implementation is here: https://github.com/andreypopp/autobind-decorator - this technique gives you better control over unexpected behavior that may come from metaprogramming
Use either new.target.prototype or Object.getPrototypeOf(this) to get the prototype object of the instantiated subclass. Then traverse the prototype chain to all superclasses, and get the own properties of each object. Don't forget non-enumerable properties.
Of course, using this in a constructor for more than logging/debugging purposes is a code smell. A class should not need to know about its subclasses. If you want to do autobindings, let each subclass constructor autobind its own methods.

ES6 class / instance properties

This is going to be a relatively long question, but one I would really like to understand. Final question formulated at the bottom of the question.
I have read the answers to this question:
ES6 class variable alternatives
The question on why this is not accepted syntax in ES6:
class MyClass {
const MY_CONST = 'string';
constructor(){
this.MY_CONST;
}
}
1) The first answer mentions:
Remember, a class definition defines prototype methods - defining
variables on the prototype is generally not something you do.
I don't get this; static variables in a class based language would appear to serve the same purpose as properties defined on the prototype in JS.
Obviously not an instance variable like a person's name, but it could be a default MAX_SPEED for a vehicle, or a counter that is shared by all instances. If the instance doesn't override the prototype's MAX_SPEED, it returns to the default. Isn't that the exact purpose of a static variable?
2) The following post (ES6 spec proposal) formulates:
There is (intentionally) no direct declarative way to define either
prototype data properties (other than methods) class properties, or
instance property.
Class properties and prototype data properties need be created outside
the declaration.
I don't see the actual difference in declaring / initialising an instance/class variable with default value within the class itself (outside the constructor)? What does it matter if it's on the prototype? If it concerns an instance variable with a default value that will be likely for all instances (yet overridable), I don't see what is the problem. So what is this intention about exactly?
3) The second answer on the question ES6 class variable alternatives confuses me (though not from a technical pov).
From within a class method that variable can be accessed as
this.constructor.foo (or MyClass.foo).
These class properties would not usually be accessible from to the
class instance. i.e. MyClass.foo gives 'bar' but new MyClass().foo is
undefined
This indicates that it is clearly possible to declare a class variable on the class (or underlying function) as implemented in this example: http://www.es6fiddle.net/iehn0hxp/
class Car{
constructor(){
//Set instance variable
this.instance_var = 220;
//Set class variable
this.constructor.class_var = 240;
}
}
var Mercedes = new Car();
var Audi = new Car();
//Instance property
console.log(Mercedes.instance_var); //220
//Class property
console.log(Car.class_var); //240
//Set instance property
Mercedes.instance_var = 120; //Well I don't know really :-)
console.log(Mercedes.instance_var); //120
//Class property
Car.class_var = 140;
console.log(Car.class_var); //140
//Can be accessed from the constructor property on the instance
console.log(Mercedes.constructor.class_var); //140
console.log(Audi.constructor.class_var); //140
So in the end it is possible to declare a static property from within the class; so I don't see what is the difference declaring it within the constructor, versus just defining it on the class, vs defining it from outside? In the end it just seems to be a trivial technical modification to put it in the constructor vs as an actual class definition (the result will be the same).
Is it really just a design choice to only make methods available?
Ultimate question:
Because I don't understand how being a prototype-language changes the
philosophy of having properties on the prototype against
static variables on a class. It looks the same to me.
I hope that my question is clear, shout if not.
I don't get this; static variables in a class based language would appear to serve the same purpose as properties defined on the prototype in JS.
No, static variables are more like properties defined on the constructor. Variables on the prototype would be closer to instance variables, but they’re not nearly as useful because they’re shared between instances. (So if you modify a mutable property on the prototype, it will be reflected in all other instances of that type.)
This also answers your other questions, I think, but to recap:
variables on the prototype are not like static variables in that they appear to belong to every instance rather than just the class
variables on the prototype are not like instance variables in that each instance of the class doesn’t have its own instance of the variable
therefore, variables on the prototype are not that useful and they should be assigned to in the constructor (instance variables) or assigned to the constructor (class variables)
they’re also properties, not variables
And a non-ES6-sugared example:
function Something() {
this.instanceProperty = 5;
}
Something.staticProperty = 32;
Something.prototype.prototypeProperty = 977;
var s = new Something();
console.log(s.instanceProperty); // 5
console.log(s.prototypeProperty); // 977? If you want a class property,
// this is not what you want
console.log(s.staticProperty); // undefined; it’s not on the instance
console.log(Something.staticProperty); // 32; rather, it’s on the class
console.log(Something.prototypeProperty); // undefined; this one isn’t

Categories