Use variable string as class name - javascript

I want to loop through a list of existing classes and call their methods.
The simplified version looks like this:
class Animal {
speak() {
return this;
}
}
const classNameStr = 'Animal';
eval(`${classNameStr}.prototype.speak()`);
But eval() is obviously bad practice. Is there an alternative?

You could implement a factory pattern https://medium.com/front-end-weekly/understand-the-factory-design-pattern-in-plain-javascript-20b348c832bd

You don't need eval, you need objects.
"use strict";
const animalClasses = {
Animal: class {
speak() {
return this;
}
},
Wolf: class extends Animal {
speak() {
console.log("Wooooooooo");
return super.speak();
}
}
};
function speakAnimal(classNameStr) {
animalClasses[classNameStr].prototype.speak();
}
However calling methods via the prototype like that is error prone and flat out weird.
We can and should revise it to
function speakAnimal(classNameStr) {
const animal = new animalClasses[classNameStr];
animal.speak();
}
More generally, one would write something like
"use strict";
const createAnimal = (function() {
const animalClasses = {
Animal: class {
speak() {
return this;
}
},
Wolf: class extends Animal {
speak() {
console.log("Wooooooooo");
return super.speak();
}
}
};
return function (animalName) {
return new animalClasses[animalName];
}
}());
And then consume it like this
const name = "Wolf"
const animal = createAnimal(name);
animal.speak();

Related

nested properties on a class instance

I need to build a structure (i need to do it for jestjs, in order to stub a module) that allows me to access a method in the following way:
// I make an instance of a class
const myTest = new Test()
// I access a method in this way
const result = myTest.prop1.prop2.prop3.getAll()
A basic class would look like
class Test2 {
constructor(){}
//getter
getAll(){
return 'Salutes'
}
}
And I can access getAll()
const myTest2 = new Test()
const result = myTest.getAll()
//in this way result contains a string (*salutes*)
So how can I add more properties in the middle? I found something, but in typescript... I need to do it in javascript
Assuming you need exactly the structure you've specified, in Test you'd have to create the object and have any methods you need be arrow functions or bound functions (if they need to access information from the Test instance; your example didn't so it wouldn't matter whether they had the right this). For instance:
class Test {
constructor() {
this.prop1 = {
prop2: {
prop3: {
getAll: () => {
return "Salutes";
},
getAll2() { // Also works
return "Salutes";
},
getAll3: function() { // Also works
return "Salutes";
}
}
}
}
}
}
const myTest = new Test();
const result = myTest.prop1.prop2.prop3.getAll();
console.log(result);
console.log(myTest.prop1.prop2.prop3.getAll2());
console.log(myTest.prop1.prop2.prop3.getAll3());
Example of using information on the Test instance (it's the same except for the constructor parameter and message property):
class Test {
constructor(message) {
this.message = message;
this.prop1 = {
prop2: {
prop3: {
getAll: () => {
// `this` refers to the `Test` instance because
// this is an arrow function
return this.message;
}
// `getAll2` and `getAll3` wouldn't work because
// they would get `this` based on how they're called
// (it would be the `this.prop1.prop2.prop3` object).
}
}
}
}
}
// I make an instance of a class
const myTest = new Test("Salutes encore!");
// I access a method in this way
const result = myTest.prop1.prop2.prop3.getAll();
console.log(result);
If you want to achieve it without modifying the Test2 class, you can use Proxy class and define custom handler for get method:
class Test2 {
constructor(){}
getAll(){
return 'Salutes'
}
}
let instance = new Test2();
const handler = {
get: function(target, prop, receiver) {
if (['prop1', 'prop2', 'prop3'].includes(prop)) {
return receiver;
}
return Reflect.get(...arguments);
}
};
const proxy = new Proxy(instance, handler);
console.log(proxy.prop1.prop2.prop3.getAll());
console.log(proxy.prop7);
console.log(proxy.getAll());

How to inject custom function to class in typescript

I would like to inject a custom function into my class from variable functions, how can I do this?
My code is:
const functions = {
foo: () => console.log('foo')
}
class ParentBase {
parentHello() {
console.log('parentHello')
}
}
function Base() {
class BaseClass extends ParentBase { }
BaseClass.foo = functions['foo']
return BaseClass;
}
const Class = Base();
const instance = new Class();
instance.parentHello() // it work
instance.foo() // foo is not a function
You could directly declare a new method in the class declaration:
class BaseClass extends ParentBase {
foo() {functions["foo"](); }
}
If the method name should be dynamic, you can also use computed method names:
class BaseClass extends ParentBase {
[window.location]() {functions["foo"](); }
}
If you really want to "dynamically inject" a new method (e.g. conditionally, in a loop, or some other strange construct), you'd have to write into BaseClass.prototype, which is the object BaseClass instances will inherit from:
BaseClass.prototype.foo = functions["foo"];
This will however be an enumerable property, to create a non enumerable one, use Object.defineProperty:
Object.defineProperty(BaseClass.prototype, "foo", {
value: functions["foo"],
/* enumerable: false,
writable: false */
});
Here is a small showcase:
class ParentBase {
parentHello() {
console.log('parentHello')
}
}
function Base() {
class BaseClass extends ParentBase {
instanceMethod() { console.log("BaseClass.prototoype.test", this); };
static classMethod() { console.log("BaseClass.classMethod", this); };
}
BaseClass.classMethod2 = function() { console.log("BaseClass.classMethod2", this); };
BaseClass.prototype.instanceMethod2 = function() { console.log("BaseClass.prototype.instanceMethod2", this); };
Object.defineProperty(BaseClass.prototype, "instanceMethod3", { value() { console.log("BaseClass.prototype.instanceMethod3", this); } });
return BaseClass;
}
const Class = Base();
const instance = new Class();
Class.classMethod();
Class.classMethod2();
instance.instanceMethod();
instance.instanceMethod2();
instance.instanceMethod3();
You have 2 places to access properties:
Instance based
Class Based
What you have is adding to class but you are trying to access using instance.
What you are trying to achieve is called Mixins. These are partial utility functions list that can be shared across multiple classes.
To inject properties to instances, you can either use constructor based approach or you can inject them to prototype
class ParentBase {
parentHello() {
console.log('parentHello')
}
}
const mixin = {
foo: function() { console.log('foo') },
bar: function() { console.log('Bar') },
fooBar: function() { console.log('foo-bar') }
}
function Base() {
class BaseClass extends ParentBase {}
Object.assign(BaseClass.prototype, mixin)
return BaseClass;
}
const Class = Base();
const instance = new Class();
instance.parentHello() // it work
instance.foo() // foo is not a function
instance.fooBar()
You should also know that functions/ properties added on prototype are shared across instances. So its advice to have function but not variables as it will introduce side-effect

A way to retrieve the context without passing `.this` as parameter

Here is a little background.
So I have my parent class. Constructor receives two objects: the first one is the class where the instance of the parent object was created (using others classes which are inheriting parent class).
The second object are just some parameters
parent.js
class ParentClass {
constructor(nativeObject, params) {
this.nativeObject = nativeObject;
this.param1 = params.param1;
this.param2 = params.param2;
}
// some logic here
}
module.exports = { ParentClass };
child.js
class ChildClass extends ParentClass {
// some logic here
}
module.exports = { ChildClass };
I'm using the child class in the other class methods for creating it. I can have multiple files like below:
usingclasses.js
let { ChildClass } = require('path/to/my/child/class');
let opts = {
param1: 'idkSomeString';
param2: 42;
}
class MyCoolClass {
createChild() {
return new ChildClass(this, opts);
}
}
module.exports = { MyCoolClass };
anotherclasses.js
let { ChildClass } = require('path/to/my/child/class');
let opts = {
param1: 'thatAstringToo';
param2: 123;
}
class AnotherCoolClass{
alsoCreatesChild() {
return new ChildClass(this, opts);
}
}
module.exports = { AnotherCoolClass};
I can create multiple instances of the ChildClass in a different methods within one class:
thirdexample.js
let { ChildClass } = require('path/to/my/child/class');
let opts1 = {
param1: 'aStringAgain';
param2: 27;
}
let opts2 = {
param1: 'ABC';
param2: 33;
}
class ClassAgain{
firstMethod() {
return new ChildClass(this, opts1);
}
secondMethod() {
return new ChildClass(this, opts2);
}
}
module.exports = { ClassAgain };
The reason why I'm passing .this because I'm working with it in ParentClass
e.g for logging console.log(`I know this was created in ${this.nativeObject .constructor.name}`);
What I'm looking for is the way to get rid of passing .this every time I'm creating a new instance of the ChildClass
Reason behind this is that in one file (class) I can create up to 10+ instances of the ChildClass.
In the previous question I already got an answer which helped me to make the code more structured and more easy to maintain:
Instead of:
createChild() {
return new ChildClass(this, param1, param2, param3, paramN);
}
I'm doing:
createChild() {
return new ChildClass(this, paramObj);
}
So now I can store all my objects with parameters at the top of the file (of even separate file) and easily find and change it if needed.
...
Now I'm trying to find a way to get rid of .this every time I'm creating ChildClass
Is there any other way how ParentClass can know where does this ChildClass instances "lives"?
I'm trying to figure out can I do something using .apply or .call but seems like this is not a droids I'm looking for
UPDATE:
Since I have zero progress on this and I even got a comment that most likely this is not possible let's assume for now that I need a name of the class that is creating instance of the ChildClass only for logging.
How can I retrieve the name of the class without passing it every time when creating a ChildClass?
let opts = {
className: 'MyCoolClass'
param1: 'idkSomeString';
param2: 42;
}
class MyCoolClass {
createChild() {
return new ChildClass(opts);
}
}
module.exports = { MyCoolClass };
The problem is that I will have to set className: 'MyCoolClass' in each version of opts again and again and again... Just repeat the same for 10-15 times
You can just instantiate your classes using Reflect.construct.
class ParentClass {
constructor (nat, x) {
this.nat = nat
this.x = x
}
log () {
console.log('from ', this.nat.constructor.name, this.x)
}
}
class ChildClass extends ParentClass{}
class ClassAgain{
firstMethod () {
return this.makeInstance({ a: 1 })
}
secondMethod () {
return this.makeInstance({ b: 2 })
}
makeInstance (params) {
return Reflect.construct(ChildClass, [this, params])
}
}
const ca = new ClassAgain()
ca.firstMethod().log()
ca.secondMethod().log()

How to call a function within a class from another file

I'm trying to figure out how to accomplish the following task. I want to be able to call a function that is within a class in another file:
file1
export class Something {
constructor() {
...
}
myFunction = () => {
...
}
}
file2
import { Something } from 'file1';
export function theFunction() {
if (condition met) {
Something.myFunction(...) // The myFunction is saying it's not a function
}
}
class Canonical { /* in ES5 */ function Canonical() {}
myMethod() { ... } Canonical.prototype.myMethod = function() { ... };
}
You can call that like this:
Canonical.prototype.myMethod();
// Or, to call it as a method on anObject
Canonical.prototype.myMethod.call(anObject);
What you have created however is not a method, but a property created on each individual instance, that happens to be a function:
class Unusual { /* in ES5 */ function Unusual() {
myFunction = () => { ... }; this.myFunction = function() { ... };
} }
It only exists on instances and therefore you have to create one to call it:
new Unusual().myFunction();
However I can't recommend this way of defining a "method", unless you specifically need it to be pre-bound. That is useful in React.Component classes, but now with React hooks that use case is waning.
class Test {
constructor() { this.a = '🍏'; }
myMethod() { console.log(this.a); }
myFunction = () => console.log(this.a);
}
const methodReference = new Test().myMethod;
try {
methodReference(); /* This will not work.
This is where an instance property can be advantageous */
} catch (ex) { console.error(ex.message); }
const functionReference = new Test().myFunction;
functionReference();

TypeScript - how to inherit class and override lambda method

I have an inherited class, and need the parent to have a virtual method, which is overridden in the child class. This method is called from the base constructor, and needs access to instance properties, so it needs to be a lambda function, so "this" is "_this". The problem is, overriding a lambda method does not work for me like overriding a non-lambda does. Is this possible? If not, I'd like to understand why.
Also, will "this" always be the same as "_this" when the method is only called from the constructor?
class Base {
protected prop = null;
constructor() {
this.init();
this.initLambda();
}
init() {
console.log("Base init");
}
initLambda = () => {
console.log("Base initLambda");
}
}
class Derived extends Base {
constructor() {
super();
}
init() {
console.log("Derived init");
}
initLambda = () => {
//let x = this.prop;
console.log("Derived initLambda");
}
}
Output:
Derived init
Base initLambda
Well, you can't have that.
There's an issue that was opened but it was closed as "by design".
You should use regular methods:
class Base {
protected prop = null;
constructor() {
this.init();
this.initLambda();
}
init() {
console.log("Base init");
}
initLambda() {
console.log("Base initLambda");
}
}
class Derived extends Base {
constructor() {
super();
}
init() {
console.log("Derived init");
}
initLambda() {
console.log("Derived initLambda");
}
}
And then it will work.
As for keeping the right this, you can always pass a call to the method as an arrow function:
doit() {
setTimeout(() => this.init(), 1);
}
Or use the Function.prototype.bind function:
setTimeout(this.init.bind(this));
Also, the _this thing that the typescript compiler produces is just a hack to polyfil the arrow functions for ES5, but if you change the target to ES6 then it won't use it.
Edit:
You can save the bound methods as members:
class Base {
...
public boundInit: () => void;
constructor() {
...
this.boundInit = this.initLambda.bind(this);
setTimeout(this.boundInit, 500);
}
...
With that, when I do new Derived() this is what I get:
Derived init
Derived initLambda // after 200 millis
The problem is that your lambda is a property.
When compiled to javascript, the Baseclass becomes
var Base = (function () {
function Base() {
this.prop = null;
this.initLambda = function () {
console.log("Base initLambda");
};
this.init();
this.initLambda();
}
Base.prototype.init = function () {
console.log("Base init");
};
return Base;
}());
As you can see initLambda is defined inside the constructor of Base, so there is no way you can override that.
Calling super() calls the Base constructor which defines the this.initLambda with the code in Base and runs it. Hence your result.
View on playground
Try using using a getter property.
i.E.
get initLambda() {
return () => {
console.log("...");
}
}
In such way that the whole code looks like that:
class Base {
protected prop = null;
constructor() {
this.init();
this.initLambda();
}
init() {
console.log("Base init");
}
get initLambda() {
return () => {
console.log("Base initLambda");
}
}
}
class Derived extends Base {
constructor() {
super();
}
init() {
console.log("Derived init");
}
get initLambda() {
return () => {
//let x = this.prop;
console.log("Derived initLambda");
}
}
}

Categories