This is my first question on Stackoverflow so please dont run over me like a bulldozer if i did something wrong :)
I need to know if its possible in JavaScript classes to know, if the child has provided a constructor.
E.g.
class Parent {
constructor() {
console.log('Child has constructor:', /* Magic here */)
}
}
class Child extends Parent {}
new Child()
Expected output: Child has constructor: false
Vs:
class Parent {
constructor() {
console.log('Child has constructor:', /* Magic here */)
}
}
class Child extends Parent {
constructor() {
super()
}
}
new Child()
Expected output: Child has constructor: true
Background: I would like to have a class that behaves differently when it was extended than if it was used directly.
Since Childs should provide the Parent different informations than if it was used directly.
You could add one parameter to the Parent constructor that is false by default and then when you call the super inside the Child class you pass true for this parameter.
class Parent {
constructor(called = false) {
console.log('Child has constructor:', called)
}
}
class Child extends Parent {
constructor() {
super(true)
}
}
class ChildTwo extends Parent {}
new Child()
new ChildTwo()
No, it's not possible.
The class syntax in JavaScript is just a syntax suger of a normal function.
Take this ES6 example:
class Parent {
constructor(value){
this.example = value;
}
parentMethod(){
console.log('parent:', this.example);
}
}
class Child extends Parent {
childMethod(){
console.log('children:', this.example);
}
}
const parent = new Parent('Hello');
const child = new Child('World');
parent.parentMethod();
child.childMethod();
console.log(parent.constructor);
console.log(child.constructor);
As you can see, even if you don't explicitly define a constructor, a class will always have a constructor.
The above could roughly translate into the below ES5 code which does not yet support class syntax:
function Parent(value){
this.example = value;
}
Object.defineProperties(Parent.prototype, {
parentMethod: {
writable: true,
enumerable: false,
configurable: true,
value: function(){
console.log('parent:', this.example);
}
}
});
function Child(value){
Parent.call(this, value);
}
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;
Child.prototype = Object.defineProperties(Child.prototype, {
childMethod: {
writable: true,
enumerable: false,
configurable: true,
value: function(){
console.log('child:', this.example);
}
}
});
var parent = new Parent('Hello');
var child = new Child('World');
parent.parentMethod();
child.childMethod();
console.log(parent.constructor);
console.log(child.constructor);
As you can see:
A class is merely just a function.
The .constructor is always assigned.
Hence, there is no way for you to check if child class has a constructor because constructor is always there.
Even if you can do it (which you can't), the Parent will not know beforehand what class will extend it, so it will not know whether or not a future child will have a constructor or not.
I need to know if its possible in JavaScript classes to know, if the child has provided a constructor.
There are no classes without a constructor. Some classes might have an implicit constructor (no constructor in the class syntax), but you cannot - and should not - detect that.
I would like to have a class that behaves differently when it was extended than if it was used directly
You can distinguish a new Child from a new Parent call using new.target == Parent.
Childs should provide the Parent different information than if it was used directly.
That's a bad idea. Your parent constructor should have an interface that doesn't care about who is using it. I would suggest to either
never use your parent class directly, but extend it yourself and provide that class for default usage. It can handle the different information.
make the parent class accept different kinds of information, i.e. overload its interface. Distinguish what information you were given, and behave accordingly. However, it should not matter whether the information was given directly by a user or produced by a child class.
Related
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)
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()
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.
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'))
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();
I have a class that needs a reference to a property from it's parent. I would like to know if it is better to pass in the property to the constructor of the child class and set it as a new property of the child class, or while in the parent class add it as a prototype of the child class.
implemenatation A:
// parent-class.js
let childClass = require('childClass.js');
class foo {
constructor() {
this.initChild = new childClass(this.superCoolMethod);
}
superCoolMethod() {
return 'this method does super cool stuff and the child needs it';
}
}
// child-class.js
class bar {
constructor(superCoolMethod) {
this.superCoolMethod = superCoolMethod;
}
callCoolMethod() {
this.superCoolMethod();
}
}
implemenatation B:
// parent-class.js
let childClass = require('childClass.js');
class foo {
constructor() {
this.childClass.prototype.superCoolMethod = superCoolMethod;
this.initChild = new childClass(this.superCoolMethod);
}
superCoolMethod() {
return 'this method does super cool stuff and the child needs it';
}
}
// child-class.js
class bar {
callCoolMethod() {
this.superCoolMethod();
}
}
Which implementation would be more performant and are there any better ways to achieve this?
Which implementation would be more performant...
It doesn't matter. It's a massively bad idea for class foo to reach out and change the prototype of class childClass. (But the instance property would be very-very-very-slightly faster, because the property lookup stops at the instance, rather than not finding it at the instance and then needing to look to the prototype. In the real world, the odds of it making any noticeable difference at all are near zero.)
Remember: That prototype is used by all other childClass instances, even those completely unrelated to foo code. The link between instance and its prototype is a link; instances don't get a copy of their prototype. So for example:
class Example {
};
const e = new Example();
console.log(e.foo); // undefined
Example.prototype.foo = 42;
console.log(e.foo); // 42
and are there any better ways to achieve this?
Set it as a property on the instance (e.g., implementation A or similar).
I would set it as a property. It is the way it makes sense. If you change the prototype, you will have the added method in all subsequent new childClass() objects. That may not be desired behavior.
When I do a console.log(object) I expect to see the name of the object's class. So it seems rather unexpected that a child class carries the name of its parent.
"use strict";
class Parent {
constructor () {
}
}
class Child extends Parent {
constructor () {
super();
}
}
class Grandchild extends Child {
constructor () {
super();
}
}
var grandchild = new Grandchild();
console.log(grandchild); // Parent {}
console.log(grandchild.constructor.name); // Grandchild
console.log(grandchild instanceof Parent); // true
console.log(grandchild instanceof Child); // true
console.log(JSON.stringify(grandchild)); // {}
Is this intended behaviour? Is it the console.log that's messing it up, or does JavaScript consider instances of any descendant class to be, first and foremost, the instance of the root level class?
console is not standard, as you can see in its MDN entry. The standard way to get the class name of an instance in ES6 is to use instance.contructor.name. This is stated in the spec.