How to extend a parent class' instance variable in javascript - javascript

I am trying to extend a parent class's instance variable but flow js is complaining that this is not correct. Is there something I am missing?
// BaseClass
export type AdType = {
dom: HTMLElement,
};
export default class AdsRefresh {
ads: AdType[] = [];
constructor(configs) {
this.ads = configs;
}
}
// ChildClass
import type {AdType as BaseAdType, PlaceholderType} from './adsRefresh';
export type AdType = {
placeholderIndex?: number
} & BaseAdType;
class AdsRefreshTiler extends AdsRefresh {
ads: AdType[] = [];
constructor(configs) {
super(configs);
this.ads = this.getAds(configs);
}
}
Cannot extend `AdsRefresh` [1] with `AdsRefreshTiler` because property `placeholderIndex` is missing in `AdType` [2] but exists in object type [3] in property `ads`.Flow(InferError)

It doesn't look like Flow supports overriding types and is complaining about the type conflict for the "ads" field in the parent and the child. You aren't allowed to change the type of a field that's been defined in the parent in the child.
This is so the child parent relationship is maintained. If you change the type of one of the fields on a child class the functions that you defined in the parent may no longer function when you call them on the child.
e.g.
export default class Parent {
felid1: number;
parentFunction() {
return this.felid1 / 3;
}
}
class Child extends Parent {
field1: string; // Now the parentFunction wont work since you can't divide strings
}
var a = new Parent();
a.felid1 = 1;
a.parentFunction(); // Does not crash
var c = new Child();
c.field1 = "a";
c.parentFunction(); // Crashes
You'll have to restructure you objects so this doesn't happen. Either by breaking down ads into multiple fields or by not using extends.

Related

Javascript class composition : access attribute of secondary class in attribute of main class

Let's take this exemple :
class A {
attrA = 3;
}
class B {
constructor() {
this.a = new A;
}
attrB = this.a.attrA;
methB() { console.log(this.a.attrA);}
}
const test = new B;
console.log(test.attrB);
test.methB();
I can access the class A attribute through method of class B, but I can't use it with attribute of class B, I have an "undefined" error.
The only way to do this is :
class A {
attrA = 3;
}
class B {
constructor() {
this.a = new A;
this.z = this.a.attrA
}
methB() { console.log(this.a.attrA);}
}
const test = new B;
console.log(test.z);
test.methB();
Why I need to put the attribute in the constructor and not the method?
Thanks!
You can think of class fields as being hoisted, meaning this:
class Example {
constructor() {/*...*/}
property = "value";
}
Is "actually" this:
class Example {
property = "value";
constructor() {/*...*/}
}
Or (similar to) this:
class Example {
constructor() {
this.property = "value";
/*...*/
}
}
Further, identifiers are resolved upon access. So by the time you execute test.methB(), test.a has been initialized, allowing the method to correctly resolve this.a.attrA. It works the same as this code:
let variable = null;
const logVariable = () => console.log(variable); // You may think this logs null, but ...
variable = "value";
logVariable(); // ... it actually logs `variable`'s value at the time of calling.
As you have observed, mixing property initializations from within the constructor and using field initializers may be confusing. Therefore, a more intuitive way to write your code would be:
class A {
attrA = 3;
}
class B {
a = new A;
attrB = this.a.attrA;
constructor() {}
methB() {
console.log(this.a.attrA);
}
}
const test = new B;
console.log(test.attrB); // Works now!
test.methB();
Personal recommendations:
Declare all instance and class fields.
Prefer field initializers.
Only reassign/initialize fields in the constructor for non-default values.
You may want to read on for more details for a better technical understanding.
Class syntax
Classes are just uncallable constructor functions:
class Example {}
console.log(typeof Example); // "function"
Example(); // Error: cannot be invoked without `new`
This is a quirk of the ES6 class syntax. Technically, class constructors are still "callable", but only internally (which happens when using new). Otherwise constructors would serve no purpose.
Another aspect of the class syntax is the ability to call super() in the constructor. This only comes into play when a class inherits, but that comes with yet its own quirks:
You cannot use this before calling super:
class A {};
class B extends A {
constructor() {
this; // Error: must call super before accessing this
super();
}
}
new B();
Reason being, before calling super no object has been created, despite having used new and being in the constructor.
The actual object creation happens at the base class, which is in the most nested call to super. Only after calling super has an object been created, allowing the use of this in the respective constructor.
Class fields
With the addition of instance fields in ES13, constructors became even more complicated: Initialization of those fields happens immediately after the object creation or the call to super, meaning before the statements that follow.
class A /*no heritage*/ {
property = "a";
constructor() {
// Initializing instance fields
// ... (your code)
}
}
class B extends A {
property = "b";
constructor() {
super();
// Initializing instance fields
// ... (your code)
}
}
Further, those property "assignments" are actually no assignments but definitions:
class Setter {
set property(value) {
console.log("Value:", value);
}
}
class A extends Setter {
property = "from A";
}
class B extends Setter {
constructor() {
super();
// Works effectively like class A:
Object.defineProperty(this, "property", { value: "from B" });
}
}
class C extends Setter {
constructor() {
super();
this.property = "from C";
}
}
new A(); // No output, expected: "Value: from A"
new B(); // No output, expected: "Value: from B"
new C(); // Output: "Value: from C"
Variables
Identifiers are only resolved upon access, allowing this unintuitive code:
const logNumber = () => console.log(number); // References `number` before its declaration, but ...
const number = 0;
logNumber(); // ... it is resolved here, making it valid.
Also, identifiers are looked up from the nearest to the farthest lexical environment. This allows variable shadowing:
const variable = "initial-value";
{
const variable = "other-value"; // Does NOT reassign, but *shadows* outer `variable`.
console.log(variable);
}
console.log(variable);

Is there a more direct way to achieve this in Javascript?

I have an inheritance like this:
class Parent {
some_object_property = ["some_object_property_depending_on_initialization"];
constructor() {...}
}
class Child extends Parent {
some_object_property = [...super.some_object_property, 'child_property']
}
Is there a way to let the Child inherit and extend object property some_object_property from the Parent?
I know I can achieve the end result through getter and setter. Is there a more direct way?
Did you mean this?
class Parent {
constructor() {
this.someProp = ['someprop test', 'something else'];
}
}
class Child extends Parent {
constructor() {
super();
this.someProp = [...this.someProp ,'added child prop'];
}
}
let myChild = new Child();
console.log(myChild.someProp);
It produces an error saying "super is not an identifier recognizable".
Yes, you cannot use super in a class field initialiser. Either put that line inside the constructor where it belongs (like in #DannyEbbers' answer), or just use the this keyword instead - it's just as good, no need to use super here:
class Parent {
some_object_property = ["some_object_property_depending_on_initialization"];
}
class Child extends Parent {
some_object_property = [...this.some_object_property, 'child_property']
}
this.some_object_property will refer to the instance property that was initialised by the parent constructor before you're overwriting it with a new value - it runs as if written inside the constructor.

Inaccessible method on TS object

I am attempting to make use of a method that is stored on a Typescript class from within a Vue component.
When attempting to use a method defined on said class from another class (which just so happens to be a Typescript Vue component), an Uncaught TypeError is returned to the console saying that the method I am trying to make use of is not a function
Consider the following Paper class:
export class Paper {
paperSize: number;
documentTitle: string;
constructor() {
paperSize = 1;
documentTitle = "Arbitrary title";
}
getDocumentRatio(): number {
return ArbitraryLibrary.arbitraryNumericFunction(this.paperSize);
}
}
When trying to use this class (the Vue component) in another class like:
#Component()
export class PaperUser {
paper = new Paper();
paperUserMethod() {
...
const unimportant = this.paper.getDocumentRatio();
...
}
...
// Wherever the method gets used
this.paperUserMethod();
...
}
Execution of the method is then interrupted, because using the function in this manner creates the TypeError
Initially thinking that this might be a binding issue I attempted something more like
...
this.paper.getDocumentRatio().bind(this.paper);
...
but evidently this does not work because binding in this way would be as effective as method chaining - with the IDE (VSCode) asserting that property 'bind' does not exist on type 'number'.
On first hand, you have to set your attribut in your constructor with this.myAttribut and on second hand you are using your method out of a method implementation of you class, you can try this :
class Paper {
paperSize: number;
documentTitle: string;
constructor() {
this.paperSize = 1;
this.documentTitle = "Arbitrary title";
}
getDocumentRatio(): number {
// try to be trivial on first attempt
return this.paperSize;
}
}
class PaperUser {
paper = new Paper();
paperUserMethod() {
return this.paper.getDocumentRatio();
}
usePaperuser(){
this.paperUserMethod();
}
}

ES6 - Get child class attribute inside parent constructor method

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

How to implement singleton pattern in the following situation?

const instance = Symbol('instance');
class Parent {
number;
constructor() {
const Class = this.constructor;
if (!Class[instance]) Class[instance] = this;
return Class[instance];
}
set_number (number) {
this.number = number;
}
}
class Child extends Parent {
test () { }
}
class Child2 extends Parent {
test () { }
}
const child = new Child();
const parent = new Parent();
const child2 = new Child2();
child.set_number(1);
child.test();
child2.set_number(2);
child2.test(); // child2.test is not a function
parent.set_number(3);
console.log(child.number); // output : 1
console.log(child2.number); // output : 3 (I want it to be 2.)
console.log(parent.number); // output : 3
I implmented Singleton by assigning instances to the constructor.
I want to be implemented in singleton for each of the three classes Parent, Child, and Child2. But there is a problem, when I instantiate Parent class before other classes.
I tried to come up with good idea, but fail. Is there any way I can solve it?
test URL
Class[instance] looks up instance throughout the prototype (inheritance) chain. So in case of Child2 it starts with Child2, and if there is no property it continues with Parent, then with Object. The result is that you return an instance of Parent when you want create the instance of Child2.
Instead of checking the existence of the property by using a lookup, you should use hasOwnProperty():
if (!Class.hasOwnProperty(instance)) Class[instance] = this;
That means if Child2 itself(!) does not have an instance, then assign one to it.

Categories

Resources