JS Class Abstraction - javascript

I have been trying to create an abstract class that can modify variables from the inheriting class, but i am facing an issue when i set an empty variable in the inheriting class, the abstract class won't set it ?
this example DO NOT WORK
class Abc {
constructor() {
this.id = "x";
}
}
class Test extends Abc {
id;
}
const test = new Test();
console.log(test)
this example DO WORK
class Abc {
constructor() {
this.id = "x";
}
}
class Test extends Abc {
}
const test = new Test();
console.log(test)

You can initialise them your id with undefined but also set it an optional field in constructor in case you want to set in when creating instance.
You can't have abstract classes in JS but you can add a simple validation to prevent direct instantiations:
class AbstractClass {
constructor(id = undefined) {
if (new.target.name === AbstractClass.name) {
throw new Error("Can not create instance of Abstract class");
}
this.id = id;
}
}
class DerivedClass extends AbstractClass {}
const disallowed = new AbstractClass(); // throws error
const allowed = new DerivedClass(1); // will work fine

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);

Multiple classes that have a different name but extends the same class

I have for example this class:
abstract class MyClass {
abstract myProp: number;
constructor() {
// Some code
}
}
So I want to create multiple classes that extends this class. But I don't want to repeat it multiple times as I will have a lot of classes. So the purpose is that each class has a different name and myProp.
For example:
class FirstClass extends MyClass {
myProp = 1;
constructor() {
super();
// Some code
}
}
class SecondClass extends MyClass {
myProp = 2;
constructor() {
super();
// Some code
}
}
So I want to generate these classes (with for example a function) but the problem is that I will have to use the new keyword.
So the usage for each of these classes should be like this:
const myConst = new FirstClass();
const myConst2 = new SecondClass();
I hope this makes some sense. I just don't want to repeat every class because it has a different name and myProp.
You can create classes through a function that returns an anonymous class.
const createClass = ( prop: number ) => {
return class extends MyClass {
myProp: number;
constructor () {
super();
this.myProp = prop;
}
}
}
const FirstClass = createClass(1);
const x = new FirstClass();
console.log(x.myProp);
Or check out the answers to these questions for ideas:
ES6 Dynamic class names
Create object from class name in JavasScript ECMAScript 6
If I understand your problem correctly, you could just pass the variable that's different as an argument in the constructor.
class MyClass {
myProp: number;
constructor(myProp: number) {
this.myProp = myProp
// Some code
}
}
And then create the class instances you want.
const myConst = new MyClass(1);
const myConst2 = new MyClass(2);
Personally, I would only extend a class if I want to add some methods or properties that aren't shared. Since myProp exists in both classes, it's probably better to just pass the value to the constructor.

extending singleton class in es6

I need to extend a singleton class in JavaScript .
The problem that I am facing is that I get the class instance which I am extending from instead of only getting the methods of the class.
I have tried to remove super to not get the instance but then I got an error
Must call super constructor in derived class before accessing 'this' or returning from derived constructor
Code example:
let instanceA = null;
let instanceB = null;
class A {
constructor(options) {
if (instanceA === null) {
this.options = options;
instanceA = this;
}
return instanceA;
}
}
class B extends A {
constructor(options) {
if (instanceB === null) {
super()
console.log('real class is ' + this.constructor.name)
this.options = options
instanceB = this;
}
return instanceB;
}
}
const a = new A({
i_am_a: "aaaaaa"
});
const b = new B({
i_am_b: "bbbbbb"
}) // this change a
console.log(b.options)
console.log(a.options)
So, first of all there's a misconception here:
I have tried to remove super to not get the instance but then I got an error
super() calls the parent class' constructor on the created instance of the child class (i.e. what this is referencing). It does not return a parent class instance. See here for more information.
So, calling super() does not violate the singleton property of the parent class at all. It may well be only constructed a single time if implemented correctly.
With that in mind, you should improve your code a little bit.
A sensible change would be to remove the instance management from the constructors. One solution would be to use static constructors which either create the singleton if no instance exists or return the created instance.
Another one is to drop the arguments to the singleton class constructors. It doesn't really make sense to pass arguments to a class which is supposed to be instantiated once (you're never gonna do anything with the constructor arguments again). You could just make the arguments properties of the singleton right away. Here's a SO answer supporting this point for Java singletons.
A complete example with static constructors and without arguments looks like this:
let instanceA = null;
let instanceB = null;
let counters = { A: 0, B: 0 }; // count class instantiations
class A {
static getInstance() {
if (instanceA === null) {
instanceA = new A();
}
return instanceA;
}
whoami() {
const name = this.constructor.name;
return `${name} #${counters[name]}`;
}
constructor() {
counters[this.constructor.name] += 1;
}
}
class B extends A {
static getInstance() {
if (instanceB === null) {
instanceB = new B();
}
return instanceB;
}
constructor() {
super();
}
}
const a1 = A.getInstance();
const a2 = A.getInstance();
const a3 = A.getInstance();
const b1 = B.getInstance();
const b2 = B.getInstance();
const b3 = B.getInstance();
console.log(a1.whoami());
console.log(a2.whoami());
console.log(a3.whoami());
console.log(b1.whoami());
console.log(b2.whoami());
console.log(b3.whoami());
Note that B inherits whoami from A and that the constructor call counters are never incremented past 1.
Obviously with this approach you can make no guarantee the singleton property holds for each class unless only the static constructors are used to generate instances (since the constructors are still accessible). I think it's a good compromise though.
In JavaScript, a singleton is just an object literal.
const a = {
options: {
i_am_a: "aaaaaa"
}
};
const b = {
options: {
i_am_b: "bbbbbb"
}
};
If you really need a constructor function, you can just write a function that returns an object.
function makeSingleton(options) {
return {
options
}
}
const a = makeSingleton({i_am_a: "aaaaaa"});
const b = makeSingleton({i_am_b: "bbbbbb"});
There's no inheritance chain here, just two object literals. If you absolutely need a class, you can just create one, but it's an unnecessary waste of resources and typing.
class Singleton {
constructor(options) {
this.options = options;
}
}
const a = new Singleton({i_am_a: "aaaaaa"});
const b = new Singleton({i_am_b: "bbbbbb"});
In terms of inheriting, if that's something you really need, you can use Object.create() or Object.assign(), depending on your needs. Be aware that both are shallow - they only work a single layer deep so modifying the child's options property would modify the parent's options property as well.
const a = {
options: {
i_am_a: "aaaaaa"
},
getOptions() {
return this.options;
}
};
const b = Object.create(a);
b.options.i_am_b: "bbbbbb";
a.options.i_am_b; // -> "bbbbbb"
b.getOptions(); // -> { i_am_a: "aaaaaa", i_am_b: "bbbbbb" }
Of course, you could use Object.create() or Object.assign() on the options as well.
To be honest, I think you either need a couple of instances of the same class, or a simple object literal without any inheritance.
const instances = {}
class Singleton {
constructor() {
const instance = instances[this.constructor];
if (instance == null) {
return instances[this.constructor] = this;
}
return instance;
}
}
class Foo extends Singleton {
constructor() {
super();
this.foo = "foo";
}
}
class Bar extends Singleton {
constructor() {
super();
this.foo = "bar";
}
}
const foo1 = new Foo();
const foo2 = new Foo();
const bar1 = new Bar();
const bar2 = new Bar();
console.log(foo1 === foo2, bar1 === bar2, foo1 === bar1, foo1.foo = 123, foo2, bar1);
well i don't know if it the best solution but what i did is to check if the constructor name is different then the class name. if so then i let it create a new instance because that mean i try to extend the class
here is a working example of my test
let instanceA = null;
let instanceB = null;
class A {
constructor(options) {
this.options = options;
if (instanceA === null) {
instanceA = this;
}
if(this.constructor.name !== "A"){
return this;
}
return instanceA;
}
method1(){
console.log(this.constructor.name)
}
}
class B extends A {
constructor(options) {
if (instanceB === null) {
super(options)
instanceB = this;
}
return instanceB;
}
}
const a = new A({
i_am_a: "aaaaaa"
});a
const b = new B({
i_am_b: "bbbbbb"
})
const c = new A({
i_am_c: "ccccc"
});
const d = new B({
i_am_d: "ddddd"
})
console.log(a.options)
console.log(b.options)
console.log(c.options)
console.log(d.options)
a.method1();
b.method1();
c.method1();
d.method1();

Promoting object in JavaScript

So I have a class and another class that extends the first one.
I would like to know if it's possible to promote the extended class from the first one in JavaScript.
class Class1 {
constructor(data) {
this.var1 = data.var1
this.var2 = data.var2
}
}
class Class2 extends Class1 {
constructor(o) {
this = o
this.var3 = '!!!'
}
}
const o = new Class1({var1: 'HELLO', var2: 'WORLD'})
const o2 = new Class2(o)
console.log(o2.var1)
console.log(o2.var2)
I know that this = o is going to throw an error. But is there a way to accomplish the task without having to assign every field from the old object to a new one?
You can use the super() function:
class Class1 {
constructor(data) {
this.var1 = data.var1
this.var2 = data.var2
}
}
class Class2 extends Class1 {
constructor(o) {
super(o)
this.var3 = '!!!'
}
}
const o = new Class1({var1: 'HELLO', var2: 'WORLD'})
const o2 = new Class2(o)
console.log(o2.var1) // -> HELLO
console.log(o2.var2) // -> WORLD
More info on super(): https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/super
You can call super(o) at the beginning of the constructor on class2
Demo fiddle

Angular2 typescript create new object dynamically with class name from variable?

I tried to get to work some sort of:
export class SomeComponent {
constructor() {
let className: string = "TheClass";
/* should be the same as .. = new TheClass() */
let superSpecial = new className();
}
}
I have not yet figured out how to do this? Could anyone help me?
There are a few ways to do this. If your class is in a separate module:
SomeClass.ts
export class SomeClass {
constructor(arg: string) {
console.log(arg);
}
}
App.ts
import * as s from "./SomeClass";
var instance = new s["SomeClass"]("param");
Or using namespaces:
namespace Test {
export class SomeClass {
constructor(arg: string) {
console.log(arg);
}
}
}
var instance = new Test["SomeClass"]("param");
This will work for you
export class SomeComponent {
constructor() {
// suppose TheClass is the imported class name you want to instantiate
let className: typeof TheClass = TheClass;
/* should be the same as .. = new TheClass() */
let superSpecial = new className(); // you "new" it as normal
}
You should use square bracket notation:
const classNameStr = 'example';
const myStore = {example: class {}};
const init = new myStore[classNameStr]();
// Or in case you class si global
const classNameStr = 'example';
(window as any).example = class {}; // if your class is already global this line should be deleted. I have put it here just to make the example work
const init = new window[classNameStr]();
Or Eval:
eval(`new ${className}()`);

Categories

Resources