ES6 - Get child class attribute inside parent constructor method - javascript

I had made an abstract class:
class Model {
attributes = [];
constructor(data) {
this._importAttributes(data);
}
_importAttributes(data) {
this.attributes.forEach((attr) => {
this[key] = data[attr];
});
}
}
and then extended from that abstract class:
import Model from './model';
class Promotion extends Model {
attributes = ['id', 'shop_name', 'title', 'description', 'end_time'];
// No need to state this constructor, just want to state out the problem clearly
constructor(data) {
super(data); // <-- Problem occured in here,
// this.attributes is empty array inside parent constructor
}
}
so that I could use the class like this way:
let promotion = new Promotion({'id': 1, 'shop_name': 'Sample'....});
------ WHAT I WOULD LIKE TO ACHEIVE ------
I would like to use the _importAttributes() function inside constructor() of all extends child class. Simply just state the attributes of the child class and start to use easily.
------ PROBLEM ENCOUNTERED ------
When I call constructor() in the Promotion class,
it cannot get attributes of the Promotion class.
Appreciate for any kindly help.

There's quite a few things wrong here.
This is a syntax error:
class Model {
attributes = [];
// ...
}
You can't just define properties on a class. You need to define attributes in the constructor with something like this.attributes = ['attribute']. Since the base class constructor calls _importAttributes you can't call super() and then add the attributes in the child class because you need to call super() first.
You also have this[key] = data[attr]; but key isn't defined. I assume that should be attr.
I think a good way to do this would be to pass the attributes as a parameter to super with a default value of []. Then the parent class can add this.attributes to the instance before calling _importAttributes.
Something like:
class Model {
constructor(data, attributes=[]) { // accepts attributes
this.attributes = attributes // adds them to instance
this._importAttributes(data); // now you can use them
}
_importAttributes(data) {
this.attributes.forEach((attr) => {
this[attr] = data[attr];
});
}
}
class Promotion extends Model {
constructor(data) {
super(data, ['id', 'shop_name', 'title', 'description', 'end_time']); // pass attributes
}
}
let promotion = new Promotion({'id': 1, 'shop_name': 'Sample'})
console.log(promotion)

As of 2018, the ES6 class syntax not support define class property yet. See this link for more details. The only way to achieve what you want is to define class property in constructor function (See answer from Mark Meyer)
Also, notice that the class keyword in ES6 is just syntactic sugar to create constructor similar to other OOP languages. It's still in its early form so we need to wait for a few versions for it to fully support other features. There is a proposal in ES7 that allow defining properties within class construct

Related

Inability to set value of public class field from constructor of super if that field is redefined in the inheriting class

I use polymorphism with JSDoc to describe concrete implementations of some classes. Up until now I have been doing this with getters and setters; newly implemented public class fields can shorten this getter and setter boilerplate considerably.
My problem is described by the code snippet below. The value set in the constructor does not survive the redefining of the public class field. How can I redefine a public class field in an inheriting class and keep the value set in the super constructor? Is this a bug in public class fields?
class ThingHolder {
/**#type {notKnownYet}*/
publicFieldForThing
constructor(thing) {
this.publicFieldForThing = thing
//Do stuff for all thing holders
}
dropMyThing() {
//Throw not implemented
}
setThingField(thing) {
this.publicFieldForThing = thing
}
}
class RedThingHolder extends ThingHolder {
/**#type {RedThing}*/
publicFieldForThing
dropMyThing() {
//functionality for dropping a red thing with JSDoc niceties for RedThings
}
}
class BlueThingHolder extends ThingHolder {
/**#type {BlueThing}*/
publicFieldForThing
dropMyThing() {
//functionality for dropping a blue thing with JSDoc niceties for BlueThings
}
}
const redThing = {
colour: "red"
}
//Setting the thing in the constructor results in unextected behaviour
const redThingHolder = new RedThingHolder(redThing)
console.assert(redThingHolder.publicFieldForThing === redThing)
//Setting the thing through an inherited method after construction works perfectly fine
redThingHolder.setThingField(redThing)
console.assert(redThingHolder.publicFieldForThing === redThing)
Edit: This question was not about my motivations. Why do public fields behave differently to getters with regard to the prototype chain?
class Parent {
constructor() {
console.log("prop in instance during super: ", "prop" in this)
console.log("publicField in instance during super: ", "publicField" in this)
}
}
class Child extends Parent {
constructor() {
super()
console.log("prop in instance after super: ", "prop" in this)
console.log("publicField in instance after super: ", "publicField" in this)
}
publicField
get prop() { }
}
new Child
When you call the constructor of RedThingHolder class
new RedThingHolder(redThing)
the default constructor of RedThingHolder will pass the parameter redThing to the super class' constructor which will add a property publicFieldForThing on the newly created object.
Re-definition of publicFieldForThing in child classes overwrites the publicFieldForThing set inside the super class.
I feel like this is a bug and that the value should be retained
automatically.
Its not a bug and redefining a property with the same name will not retain a value because you are defining a field with a name that already exists on an object. Instead of retaining, it will overwrite the existing field.
Doing this
class RedThingHolder {
publicFieldForThing;
...
}
will overwrite the publicFieldForThing field with the default value of undefined.
How can I redefine a public class field in an inheriting class and
keep the value set in the super constructor?
You can set the publicFieldForThing in the child class constructor using the getter for publicFieldForThing defined in the super class.
class ThingHolder {
constructor(thing) {
this.publicFieldForThing = thing;
}
get publicField() {
return this.publicFieldForThing;
}
}
class RedThingHolder extends ThingHolder {
constructor(thing) {
super(thing);
this.publicFieldForThing = this.publicField;
}
}
const redThing = {
colour: 'red',
};
const redThingHolder = new RedThingHolder(redThing);
console.log(redThingHolder.publicFieldForThing === redThing);
Edit
Why do public fields behave differently to getters with regard to the
prototype chain?
getter/setters in ES2015's classes are added on the proptotype object whereas the public fields are added on the object itself.
In the code example you posted, prop is added on the Child.prototype whereas the publicField is like doing
this.publicField = undefined;
in the constructor of the Child class just after the super() call.
This is why "prop" in this inside the constructor of the Parent class returns true whereas "publicField" in this evaluates to true only inside the constructor of the Child class.

Getting the name of a class on which a static function has been called

So my question is as follows: Can I get the name of a class on which a function got called? To illustrate that problem I will give a short code example.
class Base {
static getClassName() {
// get the caller class here
}
}
class Extended extends Base { }
Base.getClassName(); // Base
Extended.getClassName(); // Extended
Can anyone tell me how to do this, or say if this is even possible.
The following code should work:
class Base {
static getClassName() {
return this.name;
}
}
class Extended extends Base {}
console.log(Base.getClassName()); // Base
console.log(Extended.getClassName()); // Extended
In a static method, this refers to the class object itself.
In ES6, functions have a read-only name field (doc). Since classes are functions, you can simply use MyClass.name to statically get the class name.
To get the class name of functions at runtime, reference the Function.name of the constructor function.
class Base {
getClassName() {
return this.constructor.name;
}
}
class Extended extends Base {}
console.log(Base.name);
console.log(Extended.name);
const base = new Base();
console.log(base.getClassName());
const extended = new Extended();
console.log(extended.getClassName());

JavaScript How to get subclasses? [duplicate]

I have code like this
class Animal{}
class Dog extends Animal {}
class Cat extends Animal {}
class Donkey extends Animal {}
I want to look at all of the classes in my application's universe, and when I find one that descends from Animal, I want to create a new object of that type and add it to the list. This allows me to add functionality without having to update a list of things. So I can avoid the following:
var animals = [];
animals.push( new Dog() );
animals.push( new Cat() );
animals.push( new Donkey() );
PS: I don't want to add extra functionality to my classes or call them explicitly.
I think you could make use of decorators. For instance, you could create #Extends() one and provide base class as an argument, e.g. #Extends(Animal). Inside the decorator function, you could take the name of the class decorated with #Extends and and put it into an array or an object. Don't know if it is applicable in browsers, but it should be. In Node with TypeScript I would do something like:
import { MyClassMetadata } from './';
export function Extends(parent): (...args: any[]) => void {
return (target: object): void => {
MyClassMetadata.someVariableToStoreChildren[parent.constructor.name].push(
target,
);
}
}
Then you can access the MyClassMetadata variable that stores array of children of a given class and use it the way you want. You can play with it and get the desired result.
What about this:
class Animal {
static derived = new Set();
}
class Dog extends Animal {
static dummy = Animal.derived.add(this.name);
}
class Cat extends Animal {
static dummy = Animal.derived.add(this.name);
}
class Donkey extends Animal {
static dummy = Animal.derived.add(this.name);
}
console.log(Animal.derived);
I tried this in a TypeScript environment. The result:
Set(3) {"Dog", "Cat", "Donkey"}
without instantiating a class.
Here what I discovered so far
http://jsbin.com/xiroyurinu/1/edit?js,console,output
class Animal{}
class Dog extends Animal {}
class Cat extends Animal {}
class Donkey extends Animal {}
var animals = getAllSubclasses(Animal);
console.log(animals.map(function(c){ return new window[c] })) // creates objects
document.body.innerText = animals; // Dog, Cat, Donkey
and the magic
function getAllSubclasses(baseClass) {
var globalObject = Function('return this')();
var allVars = Object.keys(globalObject);
var classes = allVars.filter(function (key) {
try {
var obj = globalObject[key];
return obj.prototype instanceof baseClass;
} catch (e) {
return false;
}
});
return classes;
}
The main disadvantage of this method is that I can not use ES6 module import and have to do old fashioned and simple contatenation of files, but this is still better that nothing.
PS: still wait for better answer
UPD: and ye, i know that to use this all classes must be defined globally, that's why i search for better way to do this..
It is not possible. You can never know e.g. about local classes defined inside some function, or privately in another module. And that's by design. It would be unmodular and break encapsulation.
Also, the set of classes is not static in JavaScript. You can create new classes dynamically open-endedly.
If you think you want such functionality then I strongly suggest you're holding it wrong. What are you trying to achieve?

extend non ES6 object

Working to understand the new ES6 syntax. If I have a class like the Outlayer library that is not written in ES6 but want to extend it with an ES6 class how would that look? The key extension point is _getItemLayoutPosition
I tried something like this.
export let MyGrid = Outlayer.create('AnyGrid', {});
MyGrid._getItemLayoutPosition = function() {
// this does not get called
}
If I do new MyGrid(element)
My extended _getItemLayoutPosition never gets called.
So then I thought maybe I need to extend the class
export class AnyGrid extends Outlayer {
_getItemLayoutPosition(item) {
return {
x: 0,
y: 0
}
}
}
This gives an error Type 'typeof 'Outlayer'' is not a constructor function type.
What is the proper way to extend this class so that I can override the _getItemLayoutPosition method?
Since Outlayer.create actually creates a function that inherits from Outlayer you have to add the method on the prototype instead of the function itself
export let MyGrid = Outlayer.create('AnyGrid', {});
MyGrid.prototype._getItemLayoutPosition = function() {
// this does not get called
}
Note that the function returned when you call Outlayer.create has additional properties i.e. is not the same as creating a class that inherits from Outlayer
Please try the following:
// AnyGrid- subclass
function AnyGrid() {
Outlayer.apply(this, arguments); // call super constructor.
}
// subclass extends superclass
AnyGrid.prototype = Object.create(Outlayer.prototype);
AnyGrid.prototype.constructor = AnyGrid;
//Add/Override the method
AnyGrid.prototype._getItemLayoutPosition = function() {
// this does not get called
}
Don't forget to export AnyGrid.

Referencing static members from instance method on dynamically extended JS class

I have a base ES6 class that I dynamically extend given a configuration object, like so:
class Model {
constructor () {
// ...
}
save () {
// ...
}
}
function createModelFromConfig (config) {
const Impl = class extends Model {};
Object.assign(Impl, config);
return Impl;
}
const User = createModelFromConfig({store: new DbStore()});
In the save() method on the abstract Model, I'd like to reference the static object store, which will exist on the class that extends Model. This means, of course, that I need to reference a static member but the extended class is anonymous.
Just in a quick test using the Chrome console, I tried
function X () {}
X.prototype.doSomething = function () { console.log(this.constructor); };
function Y () {}
Y.prototype = Object.create(X.prototype);
new Y().doSomething(); // function X () {}
I don't know if this is a reliable test, but it appears that this.constructor does not reference the Impl that I extended, but instead the original base class, which isn't helpful.
A less elegant way is to add Impl.prototype.Impl = Impl; so I can use this.Impl.store in my save function, but it'd be preferable if I could access the static members of the Impl class without this.
Is my prototypal test in the console inadequate? Or is there any other way to access the constructor class in an instance method from an inherited method?
In my testing, I've concluded that Y.prototype = Object.create(X.prototype); is not an adequate equivalent to the ES6 extends implementation.
In running in the Node REPL:
class X {
constructor () {}
save () { console.log(this.constructor.z); }
}
class Y extends X {}
Y.z = 'z';
new Y().save(); // 'z'

Categories

Resources