I'm trying to write a subclass using the es6 class syntax. The subclass has some complicated logic to perform before calling the superclass constructor, so I tried factoring it out into a function. However, this seems to be impossible since this is not defined until after super() is called.
I've tried simply calling super() twice, once at the start of the constructor and once at the end, but it feels wrong and wastes the work that the superclass constructor does the first time.
class Parent {
constructor(x) {
console.log('some expensive thing with ' + x);
}
}
class Child extends Parent {
constructor() {
let x = this.f();
super(x);
}
f() {
// complicated logic
return 3;
}
}
let c = new Child();
Running the code as written results in ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor at new Child. Removing this and attempting to call f() results in ReferenceError: f is not defined at new Child.
Is there any way to factor the subclass constructor logic somewhere else, even if it's okay if this isn't bound?
I would use an initialization function separate from the constructor as that give you more control over when/if parent initialization happens.
class Parent {
constructor(x) {
this.init(x);
console.log("parent constructor does other stuff");
}
init(x) {
console.log("parent init runs")
}
}
class Child extends Parent {
constructor(x) {
super(x);
}
init(x) {
console.log("child init runs");
super.init(x); // This call is optional if you don't want to run the parent's init code
}
}
let c = new Child();
Using a static method could be a solution.
class Parent {
constructor(x) {
console.log('some expensive thing with ' + x);
}
}
class Child extends Parent {
constructor() {
let x = Child.f();
super(x);
}
static f() {
// complicated logic
return 3;
}
}
let c = new Child();
Related
I've recently started to learn about Classes in javascript and while reading some really interesting stuff I thought of trying some of my own ideas.
If you have a parent class of Parent in which you have a method of logSomething```` and a child class ofChild, with which you doclass Child extends Parent, how can you then execute the inherited method from the parent class,logSomething```, inside of the child class?
If you define a method inside of the Child class and add this.logSomething() to that method, whenever the method from the child class is called, the inherited logSomething function will indeed run, but apart from that I haven't found any way of executing the logSomething directly inside of that child class.
I've tried this.logSomething(), I've tried adding it to a object, self executing (IIFE) function and everything I could thing of but to no result.
class Parent {
constructor() {}
logSomething() {
console.log('I am logging something')
}
}
class Child extends Paren {
logSomething() // This does not work
}
Currently doing this does not work, if throws a error referring to the fact that it things your trying to define a function.
I know it should be possible in some way, if I'm not mistaking React uses something similar with life-cycle methods right? Such as componentWillMount.
How would one go about doing this?
First error is that you are extending Paren instead of Parent.
Also you cannot just throw a random statement inside in a class. It needs to be inside a function.
If you want it to run whenever you create an instance of that class it should be inside the constructor or a function that gets called by it. (note that you need to call super() at the start of the constructor.
Finally, you still need to use this.logSomething or this.logSomething
class Parent {
constructor() {}
logSomething() {
console.log('I am logging something');
}
}
class Child extends Parent {
constructor() {
super();
this.logSomething(); // Will use Parent#logSomething since Child doesn't contain logSomething
super.logSomething(); // Will use Parent#logSomething
}
}
new Child();
class Parent {
constructor() {}
logSomething() {
console.log('Parent Logging Called');
}
}
class Child extends Parent {
constructor() {
super();
this.logSomething(); // Will call Child#logSomething
super.logSomething(); // Will call Parent#logSomething
}
logSomething() {
console.log('Child Logging Called');
}
}
new Child();
You could also do this:
class Parent {
constructor() {}
logSomething() {
console.log('Parent Logging Called');
}
}
class Child extends Parent {
logSomething() {
console.log('Child Logging Called and ...');
// Careful not use this.logSomething, unless if you are planning on making a recursive function
super.logSomething();
}
}
new Child().logSomething();
You can call any function or use any property of the parent class using this, as long as the new class doesn't have its own definition for that property.
Look here for more information.
class Parent {
constructor() {}
logSomething() {
console.log('I am logging something')
}
}
class Child extends Parent {
logSomething() {
super.logSomething(); // Call parent function
}
}
a) you can't call a function there, you can call a function within a function declared in a class
b) you need to use this.logSomething()
example:
class Parent {
constructor() {}
logSomething() {
console.log('I am logging something')
}
}
class Child extends Parent {
fn() {
this.logSomething() // This does work
}
}
new Child().fn()
See other answers for when fn is called logSomething in the child class - then you'd need super.logSomething() to call the "parent" logSomething instead of the child logSomething
I have two custom elements: One and Two. One has a function. Two has a child of One and tries to call that function in its constructor. It says that the function does not exist if I call customElements.define() on Two before One. However, if I define One before Two, it works just fine.
In my actual project, I do not have control of the order in which they are defined, and by default they are being defined in the wrong order.
I tried calling the function in the connectedCallback(), but this also failed.
When exactly is the constructor called?
Is there any way that I can make sure they are all defined before any constructors are called?
class One extends HTMLElement {
constructor() {
super()
console.log('one constructor')
}
myFunc() {
console.log('it worked!')
}
}
class Two extends HTMLElement {
constructor() {
super()
console.log('two constructor')
this.innerHTML = '<my-one></my-one>'
this.myOne = document.querySelector('my-one')
// this part fails
this.myOne.myFunc()
}
connectedCallback() {
// this also fails
this.myOne.myFunc()
}
}
// this works
// customElements.define("my-one", One)
// customElements.define("my-two", Two)
// this breaks
customElements.define("my-two", Two)
customElements.define("my-one", One)
<my-two></my-two>
It is all about the life-cycle of a Web Component and when a tag is upgraded from an HTMLUnknownElement to your actual Component
A component is defined in two steps.
1) Definition of a class
2) Calling customElements.define
Yes these two can be written together:
customElements.define('dog-food', class extends HTMLElement{});
But the class definition still happens before customElements.define is called.
Elements are only upgraded to a custom element when two things have happened:
1) The Custom Element must be defined by using customElements.define and
2) The Custom Element must either
1) be instantiated using document.createElement or new MyElement
2) be added to the DOM tree
This example has the element placed into the DOM but it is not defined for 1 second.
I display the constructor before it is defined and then, again, after it is defined.
class One extends HTMLElement {
constructor() {
super();
console.log('one constructor');
}
connectedCallback() {
this.innerHTML = "I have upgraded";
}
}
let el = document.querySelector('my-one');
setTimeout(() => {
customElements.define("my-one", One);
console.log('after: ', el.constructor.toString().substr(0,30));
}, 1000);
console.log('before: ', el.constructor);
<my-one>I am just a simple element</my-one>
In your code you use innerHTML to "load" <my-one>. But since <my-two> may not "really" be in the DOM by the time the constructor is called then the innerHTML will not be in the DOM and, thus, <my-one> will not get upgraded yet.
One thing you can do is to wait until the <my-two> component is really placed into the DOM by waiting to change the innerHTML in the connectedCallback function:
class One extends HTMLElement {
constructor() {
super();
console.log('one constructor');
}
myFunc() {
console.log('it worked!');
}
}
class Two extends HTMLElement {
constructor() {
super();
console.log('two constructor');
}
connectedCallback() {
this.innerHTML = '<my-one></my-one>';
setTimeout(() => {
this.myOne = document.querySelector('my-one');
this.myOne.myFunc();
});
}
}
customElements.define("my-two", Two)
customElements.define("my-one", One)
<my-two></my-two>
You will notice that I had to still place the call to the function in <my-one> into a setTimeout call. This is because the <my-one> element can not be upgraded until AFTER your connectedCallback function exists. Upgrading needs a chance to run and it will not run in the middle of your function.
Another way to do it is by calling the constructor for <my-one> directly:
class One extends HTMLElement {
constructor() {
super();
console.log('one constructor');
}
connectedCallback() {
this.innerHTML = "one";
}
myFunc() {
console.log('it worked!');
}
}
class Two extends HTMLElement {
constructor() {
super();
console.log('two constructor');
}
connectedCallback() {
customElements.whenDefined('my-one').then(() => {
this.myOne = document.createElement('my-one');
this.append(this.myOne);
this.myOne.myFunc();
});
}
}
customElements.define("my-two", Two);
customElements.define("my-one", One);
<my-two></my-two>
Here you will notice that I had to add a call to customElements.whenDefined. This will wait until <my-one> to actually be defined before it attempts to instantiate it. Once it is defined then you can create it and call the member function right away.
One last thing. There are rules for what you should and should not do while in a constructor for a Web Component. They are defined here https://w3c.github.io/webcomponents/spec/custom/#custom-element-conformance
But one thing I will point out is that you are not supposed to touch or change any attributes or child elements in the constructor. Mainly because there are no attribute or any child elements when the Web Component is constructed. Those are changed and added after the fact.
Is It good/bad practice to call a child method from a parent class?
class Parent {
constructor() {
// if 'autoPlay' exists (was implemented) in chain
if (this.autoPlay) {
this.autoPlay(); // execute from parent
}
}
}
class ChildA extends Parent {
autoPlay() {
console.log('Child');
}
}
class ChildB extends Parent {
// 'autoPlay' wasn't implemented
}
const childA = new ChildA();
const childB = new ChildB();
Is it a good practice to call a child method from a parent class?
Yes, it's a totally normal practise. The parent class just calls some method of the instance, and if the child class has overridden the method then the child method is called. However, you usually wouldn't do such a "has my instance defined this method" test, you just would call it. If you want to do nothing by default, just define an empty method (like in #scipper's answer). If you want to make the method abstract (force child classes to override it), you can either leave it undefined or define a method that throws an appropriate exception.
Is is a bad practice to call a child method from a parent constructor?
Yes. Don't do that. (It's a problem in all languages).
The purpose of a constructor is to initialise the instance and nothing else. Leave the invocations of side effects to the caller. This will ensure that all child constructors will finish their initialisation as well.
A contrived example:
class Parent {
autoPlay() {
this.play("automatically "); // call child method
}
play(x) {
console.log(x+"playing default from "+this.constructor.name);
}
}
class ChildA extends Parent {
// does not override play
}
class ChildB extends Parent {
constructor(song) {
super();
this.song = song;
}
play(x) {
console.log(x+"playing "+this.song+" from ChildB");
}
}
const child1 = new ChildA();
child1.autoPlay();
const child2 = new ChildB("'Yeah'");
child2.autoPlay();
Notice how that would not work if the Parent constructor did call autoplay. If you don't like to need an extra method call everywhere after the instantiation, use a helper function. It might even be a static method:
class Parent {
autoPlay() { … }
play { … }
static createAndAutoPlay(...args) {
const instance = new this(...args);
instance.autoPlay();
return instance;
}
}
…
const child1 = ChildA.createAndAutoPlay();
const child2 = ChildB.createAndAutoPlay("'Yeah'");
It would be better style to define an empty implementation of autoPlay in the Parent class, and override it in the child.
class Parent {
constructor() {
this.autoPlay();
}
autoPlay() {
}
}
class Child extends Parent {
autoPlay() {
console.log('Child');
}
}
const child = new Child();
I don't want my parent class to be too long so I separate some methods from it and create child class.
However I don't want to use child class as a instance I want it to be used only by parent class.
class Parent {
parentMethod() {
this.foo(); // execute from parent
}
}
class Child extends Parent {
foo() {
console.log('foo!');
}
}
const parent = new Parent();
parent.parentMethod(); // execute parent method which execute child method
this cause:
Uncaught TypeError: this.foo is not a function
I don't want my parent class to be too long so I separate some methods from it
Ok.
So I create child class, however I don't want to use child class as a instance.
No, subclassing is the wrong approach here. Especially if you don't want to instantiate the subclass, it's not even a solution to your problem.
To separate units of code, factor them out into separate functions. Those don't need to be linked to the caller through inheritance, and don't need to be methods of a class at all. Just write
class MyClass {
myMethod() {
foo();
}
}
function foo() {
console.log('foo!');
}
const instance = new MyClass();
instance.myMethod();
Or compose your object of multiple smaller helpers:
class Outer {
constructor() {
this.helper = new Inner();
}
myMethod() {
this.helper.foo();
}
}
class Inner {
foo() {
console.log('foo!');
}
}
const instance = new Outer();
instance.myMethod();
If you want to use the Parent class in the subclass, Child class, then you need to do the following:
class Parent {
foo() {
console.log('foo!');
}
}
class Child extends Parent {
constructor() {
super();
}
}
let c = new Child(); //instantiate Child class
c.foo(); // here you are calling super.foo(); which is the Parent classes method for foo.
//foo!
The super keyword is used to access and call functions on an object's
parent.
How to use super
Or, alternatively, if you'd rather create a method on the child class that wraps the parent method of foo, rather than accessing it by instantiating the parent class via calling super in the child constructor:
class Parent {
foo() {
console.log('foo!');
}
}
class Child extends Parent {
method() {
this.foo();
}
}
let c = new Child();
c.method();
TLDR; How to directly call setter of an object's parent class without invoking the child's setter outside of both the parent and child class?
I know that if the solution exists, it may be very hacky/magic-like, but I don't mind. Here's the scenario:
Parent is class from a 3rd party library so I can't change this code at all.
Child is a class from my codebase, but I'd like to keep the magic code outside of it, as the Prop class may be use with different "Child" classes.
Prop is the class where the magic code may reside if necessary.
I need to access the Parent's setter of x via a Child object without invoking the setter of x of the Child.
Is it even possible?
class Parent {
constructor() {
this._x = 255;
}
set x(v) {
console.log("Calling Parent setter");
this._x = v;
}
get x() {
console.log("Calling Parent getter");
return this._x;
}
}
class Child extends Parent {
constructor() {
super();
this.prop = new Prop(this);
}
set x(v) {
console.log("AVOID! Calling Child setter");
super.x = v;
// Shennanigans I don't want to run
}
get x() {
console.log("Calling Child getter");
return super.x;
}
}
class Prop {
constructor(child) {
this.child = child;
}
setX() {
const parent = this.child; // Not sure what to do here.
const old = parent.x;
parent.x = 0;
console.log(`parent.x changed from ${old} to ${parent.x}`);
}
}
const child = new Child();
child.prop.setX();
Reflect.set is here to your rescue! It does allow to pass the receiver separately:
setX() {
Reflect.set(Parent.prototype, "x", 0, this.child); // invokes the Parent.protype.x setter
}
Alternatives would be Object.getOwnPropertyDescriptor(Parent.prototype, "x").set.call(this.child, 0) or just this.child._x = 0 (if you don't need to run the setter code).
So while it is possible, I would recommend to reconsider your design. Maybe inheritance is the wrong approach here, and you should use composition instead of extends Parent:
class Child {
constructor() {
this.val = new Parent();
}
set x(v) {
… // Shenanigans
this.val.x = v;
}
get x() {
return this.val.x;
}
// (without the Prop helper class for simplicity)
setX(v) {
// without shenanigans
this.val.x = v;
}
}