How to access subclass from parent in ES6? [duplicate] - javascript

This question already has answers here:
Get parent class name from child with ES6?
(5 answers)
Call static methods from regular ES6 class methods
(3 answers)
Closed 5 years ago.
I have a base class that looks like this:
class Speaker {
sayHi() {
if (this.isLoud) {
console.log('HI');
} else {
console.log('hi');
}
}
}
And a subclass that looks like:
class LoudSpeaker extends Speaker {
}
LoudSpeaker.isLoud = true;
... where that last line is an attempt at creating a class variable in ES6. The problem is that when I instantiate LoudSpeaker and call sayHi, the isLoud variable is undefined. Is there anyway of accessing a class variable of a subclass from the parent class?

this.isLoud is not the same thing as LoudSpeaker.isLoud. The first refers to an instance variable, the second refers to a property of the LoudSpeaker constructor function which other languages would call a class variable or a static property. In Javascript, you do not refer to class variables using this.isLoud.
To override the value of the instance variable isLoud in a subclass, you would set its value in the constructor.
class Speaker {
constructor() {
this.isLoud = false; // set default value
}
sayHi() {
if (this.isLoud) {
console.log('HI');
} else {
console.log('hi');
}
}
}
class LoudSpeaker extends Speaker {
constructor() {
super();
this.isLoud = true; // override to set our own value
}
}
If you really want the isLoud property to be something that a class sets once and is never set again, then you can set it on the prototype and then you can reference it with this.isLoud from within an instance:
class Speaker {
sayHi() {
if (this.isLoud) {
console.log('HI');
} else {
console.log('hi');
}
}
}
Speak.prototype.isLoud = false; // set default value
class LoudSpeaker extends Speaker {
}
LoudSpeaker.prototype.isLoud = true; // override for this class
This is usually not done in Javascript because if you set this.isLoud = whatever, then it sets an "own" property and the property becomes an instance property that is specific to that instance and that can be really confusing and bug causing. So, this is generally not done. If, however, you initialize the value on the prototype, never set it on the instance so you only read its value with x = this.isLoud, then it does behave as a class variable that is accessible via this.
There is discussion of this topic in the ES6 wiki. See ES6 class variable alternatives for details as this capability is intentionally left out of the ES6 class syntax which is why it's implemented here as either a regular instance variable set in the constructor or a property set directly on the prototype (and not via the class syntax).

Related

Not able to set properties in JavaScript class

I am simply setting properties in a class in JavaScript:
class BookingReports extends ReportsInterface{
var categoryID;
constructor() {
super();
}
init(CatID)
{
this.categoryID=CatID;
}
}
But JavaScript is not accepting variable at all and giving error "unexpected identifier".
I am not getting idea what is syntax error. Is it because of inheritance or super keyword? I even tried using binding with this whole declaration. But it is not working.
var categoryID is just out of place there. You don't declare variables at the class level; you declare variables within the constructor or methods.
In your case, you probably don't want to declare a variable at all; instead, create the property in your constructor:
class BookingReports extends ReportsInterface{
constructor() {
super();
this.categoryID = /*...some default value...*/;
}
init(CatID)
{
this.categoryID=CatID;
}
}
Side note: Having an init method doesn't make much sense; that's what the constructor is for. So:
class BookingReports extends ReportsInterface {
constructor(catID) {
super();
this.categoryID = catID;
}
}
At some point, the class fields proposal will reach Stage 4, at which point you will be able to "declare" properties at the class level if you want to:
// Valid if/when the class fields proposal advances
class BookingReports extends ReportsInterface {
categoryID; // Declares the property
constructor(catID) {
super();
this.categoryID = catID;
}
}
If you're initializing the property in the constructor, there's not a lot of point to that; but it can be useful for telling the reader (and JavaScript engine) the standard "shape" of your object up-front rather than making them (and it) analyze the constructor's code.

ES6 Accessing child class's `this` when overriding a function in javascript

I have following code:
class BaseClass {
constructor() {
// BELOW LINE CALLS OVERRIDDEN FUNCTION BUT WITH `this` OF BaseClass
this.preHook();
}
// THIS FUNCTION IS BEING OVERRIDDEN IN CHILD CLASS
preHook(data) {
return data;
}
}
class ChildClass extends BaseClass {
constructor() {
super();
this.someKey = 'someValueFromChild';
}
preHook(data) {
console.log('this.someKey', this.someKey); //IT PRINTS undefined
}
}
var c = new ChildClass();
OUTPUT:
this.someKey undefined
Expecting
this.someKey someValueFromChild
So in BaseClass's constructer i am calling a function this.preHook() which is getting overridden in ChildClass, when we initialise ChildClass it calls BaseClass's constructer and then it calls this.preHook's overridden version , but instead of printing someValueFromChild which is defined in child class, it is printing undefined (it is using this of parent class instead of child`.
How can i make it to use this of child class.
How can i make it to use this of child class.
There is no "this of child class." There is only one object (backed by its prototype objects). this within BaseClass and this within ChildClass both refer to the same object.
The reason accessing this.someKey results in undefined in that code is that the child constructor doesn't set someKey until after the parent constructor is finished — and the parent constructor calls preHook, so preHook is called before the child constructor sets someKey. So there is no someKey property at all as of when preHook tries to read it from the object. You'd see this if you stepped through the code with a debugger. Here's an annotated version showing what happens when:
class BaseClass {
constructor() {
console.log("BaseClass constructor start");
this.preHook();
console.log("BaseClass constructor finish");
}
// THIS FUNCTION IS BEING OVERRIDDEN IN CHILD CLASS
preHook(data) {
console.log("BaseClass.preHook called");
return data;
}
}
class ChildClass extends BaseClass {
constructor() {
console.log("ChildClass constructor start");
super();
console.log("ChildClass constructor setting `someKey`");
this.someKey = 'someValueFromChild';
console.log("ChildClass constructor finish");
}
preHook(data) {
console.log("ChildClass.preHook called");
console.log('this.someKey', this.someKey);
}
}
var c = new ChildClass();
.as-console-wrapper {
max-height: 100% !important;
}
Calling overrideable methods from a constructor is a well-established anti-pattern — and this is part of the reason. :-) Instead, don't have the parent constructor call preHook. Do it after construction.
There are workarounds, but the best option by far is to not have the BaseClass constructor call preHook at all. How you do that in your code obviously depends on the greater context of your code, so I can't give you an example.
There is race condition that the consequence of natural limitations of ES6 classes. this should always appear after super() in child constructor. Since this.preHook() is called in parent constructor (super), there's no way how this.someKey can be assigned in constructor and appear during this.preHook() call.
Otherwise this.preHook() call should happen in child constructor only, this defies the concept of hooks and prevents child class from being properly extended.
In order to avoid race condition, someKey should be defined on class prototype, thus super and this precedence is no longer a problem:
class ChildClass extends BaseClass {
constructor() {
super();
}
get someKey() {
return 'someValueFromChild';
}
...
}
The alternative that allows to maintain proper class lifecycle is to implement hooks on application level, like it's usually done in JS frameworks. Hookable classes should be instantiated through certain interface and never directly, i.e:
function instantiateWithHooks(cls, args) {
const instance = new cls(...args);
instance.preHook();
return instance;
}
As explained in another answer, when child class is instantiated, there is only one this, which is (usually) an instance of child class.

What's difference between two ways of defining method on React Class in ES6

I see this a lot in ES6 React code
class Foo extends React.Component {
bar = () => {
console.log("bar")
}
baz() {
console.log("baz")
}
}
Seems like they both define methods bar and baz on Foo but how are they different.
The declarations differ in how the function are written and the context of this,
In the first syntax
bar = () => {
console.log("bar")
}
the function is written using Arrow function syntax.
An arrow function does not have its own this; the this value of
the enclosing execution context is used. Hence this keyword inside
this function will refer to the context of the React class
However the second declaration
baz() {
console.log("baz")
}
is a simple function and this keyword in this function refers to the context of the function itself.
So when you try to access React class Properties/functions like this.state or this.setState you will get an error in the second case(if you haven't used binding anywhere else for this function(example constructor)) whereas it would work in the first case since for an arrow function, this means the same thing within the function body as it does outside of it. Which means that if you use arrow functions within your component’s custom functions, they can use this and this.setState with no surprises.
Check this answer on why you need to bind functions in React classes
The previous answers are absolutely correct and are why you would want to be using arrow functions in react classes.
I just wanted to also point out a subtle difference that is a potential pitfall of their use in a class to avoid surprises...
Arrow functions defined on a class are added to the instance as a property that just so happens to be a function, whereas defining not as an arrow function will add the function as a method to the class's prototype.
In React components that will never be extended this is fine, but if the case arises you want to subclass a component, you will not be able to override an arrow function expecting to be able to call the base class via super, you can only overwrite it in its entirety
class Button extends React.PureComponent {
// class method - overridable in subclass
pressMethod: function(): void {
console.log('beep')
}
// instance property functions - only overwriteable in subclass
pressArrow = (): void => {
console.log('boop')
}
pressArrowTwo = (): void => {
console.log('blip')
}
}
class BigRedButton extends Button {
// class method - overides subclass one,
// can call superclass method via `super`
pressMethod: function(): void {
super.pressMethod() // prints 'beep' to console
console.log('BOOOOOOOOM!!!')
}
// instance property function
// cannot call superclass via `super` as lambda's have no prototype
pressArrow = (): void => {
super.pressArrow() // TypeError - not a function
console.log('BOOOOOOOOM!!!')
}
// completely overwrites instance property of same name in subclass
// doesn't try to access prototype so won't error but is limited
pressArrowTwo = (): void => {
console.log('BOOOOOOOOM')
}
}
For simplicity, both are equal:
bar = () => { ... }
this.bar =this.bar.bind(this)

Reactjs: In JavaScript, class methods are not bound by default

I'm following reactjs handling-events documentation, and I came across this:
In JavaScript, class methods are not bound by default. If you forget to bind this.handleClick and pass it to onClick, this will be undefined when the function is actually called.
What I understand from this quotation is that this doesn't designate the current instantiation in every class methods.
However, if I consider the code below, the method editState can access the property state of the extended class Parent through this. Which would make false the previous quotation, as this is bound to all the class mehtods.
class Parent {
constructor() {
this.state = false;
}
setState(newState) {
this.state = newState;
}
}
class Child extends Parent {
editState() {
this.setState(true);
}
}
let c = new Child;
c.editState();
console.log(c.state);
What am I missing here?
When a function is used as an event handler, its 'this' is set to the element the event fired from.
As a DOM event handler
I think the React documentation might be misleading. The React component below MyClass is a derived class (aka child class) of React.Component. The constructor method is a special method within javascript ES6 which is used for creating and initializing an object created with a class. React uses the constructor to pass props to the component when the object is created. Base classes (aka parent classes) are created bound to their own this, but the derived classes do not have their own this by default. The derived class must call super(props) which binds the this from the base class down into the derived class.
class MyClass extends React.Component{
constructor(props) {
super(props)
}
}
Here is the relevant information from the link provided by user376830's answer. This is straight from the javascript documentation.
Class context
The behavior of this in classes and functions is similar, since classes are functions under the hood. But there are some differences and caveats.
Within a class constructor, this is a regular object. All non-static methods within the class are added to the prototype of this:
class Example {
constructor() {
const proto = Object.getPrototypeOf(this);
console.log(Object.getOwnPropertyNames(proto));
}
first(){}
second(){}
static third(){}
}
new Example(); // ['constructor', 'first', 'second']
Note: Static methods are not properties of this. They are properties of the class itself.
Derived classes
Unlike base class constructors, derived constructors have no initial this binding. Calling super() creates a this binding within the constructor and essentially has the effect of evaluating the following line of code, where Base is the inherited class:
this = new Base();
Warning: Referring to this before calling super() will throw an error.
Derived classes must not return before calling super(), unless they return an Object or have no constructor at all.
class Base {}
class Good extends Base {}
class AlsoGood extends Base {
constructor() {
return {a: 5};
}
}
class Bad extends Base {
constructor() {}
}
new Good();
new AlsoGood();
new Bad(); // ReferenceError
William's answer is the most correct after following the link he provided. I'm just posting this to save anybody the time. It is quite a read. Regarding this question when you pass a function as a variable, you need to be careful about the context it is going to be used in.
From my understanding, when a method is fired from a class it's 'this' gets bound to the class object. When it is fired from an event handler it's 'this' gets bound to the button element that fired the event. Since button elements aren't javascript objects, they do not have a 'this' property hence the undefined value.

Cannot use child class' properties within parent class constructor via Babel's transform-class-properties

When extending a parent class and declaring the child class' properties via Babel's 'transform-class-properties' plugin, any class properties of the child class are not accessible via the parent class' constructor method.
class One {
internal = 1;
constructor() {
console.log('constructor internal', this.internal);
}
}
class Two extends One {
internal = 2;
}
new Two();
In the example above, 'constructor internal 1' will be output to the console. When looking at the compiled code this is obvious as to why, with the parent class being executed first and the resulting object then being integrated with the child class.
Apologies if this is by design, but it has confused me as the following code works in the way I am expecting within non-constructor methods (so the boot() method references the child class' 'internal' property value):
class One {
internal = 1;
constructor() {
console.log('constructor internal', this.internal);
}
boot() {
console.log('boot internal', this.internal);
}
}
class Two extends One {
internal = 2;
constructor() {
super();
this.boot();
}
}
new Two();
So, even when calling a method declared on the parent class, it will inherit the child class' properties. It is just constructor methods which seemingly do not behave as expected (at least by me - again, apologies if this is wrongly interpreted, but there are no caveats listed on the relative Babel page.)
Thank you.
I think that is natural. If you wish to override the parent class's property init value, you should do it in the derived class's constructor.
class Two extends One {
constructor() {
// Call parent constructor.
super();
// Override here.
this.internal = 2;
}
}
Hope it helps. Happy coding (:
loganfsmyth answered my question very clearly here: https://phabricator.babeljs.io/T7567 (Thanks!)
This is indeed expected behavior. Babel's implementation is a little incorrect because in a perfect world this would also throw an error, because you shouldn't have the same property defined in a parent and a child, but we don't do that right at the moment.
Class properties are initialized when:
For base classes, before the constructor executes
For child classes, at the end of super()
and when a given constructor initializes it's bindings, it uses that classes bindings, it doesn't know of anything in child classes. That means your One class knows the value should be 1, so that's what it sets.

Categories

Resources