How to get ES6 class constructor from class name? [duplicate] - javascript

I want create object factory using ES6 but old-style syntax doesn't work with new.
I have next code:
export class Column {}
export class Sequence {}
export class Checkbox {}
export class ColumnFactory {
constructor() {
this.specColumn = {
__default: 'Column',
__sequence: 'Sequence',
__checkbox: 'Checkbox'
};
}
create(name) {
let className = this.specColumn[name] ? this.specColumn[name] : this.specColumn['__default'];
return new window[className](name); // this line throw error
}
}
let factory = new ColumnFactory();
let column = factory.create('userName');
What do I do wrong?

Don't put class names on that object. Put the classes themselves there, so that you don't have to rely on them being global and accessible (in browsers) through window.
Btw, there's no good reason to make this factory a class, you would probably only instantiate it once (singleton). Just make it an object:
export class Column {}
export class Sequence {}
export class Checkbox {}
export const columnFactory = {
specColumn: {
__default: Column, // <--
__sequence: Sequence, // <--
__checkbox: Checkbox // <--
},
create(name, ...args) {
let cls = this.specColumn[name] || this.specColumn.__default;
return new cls(...args);
}
};

There is a small & dirty way to do that:
function createClassByName(name,...a) {
var c = eval(name);
return new c(...a);
}
You can now create a class like that:
let c = createClassByName( 'Person', x, y );

The problem is that the classes are not properties of the window object. You can have an object with properties "pointing" to your classes instead:
class Column {}
class Sequence {}
class Checkbox {}
let classes = {
Column,
Sequence,
Checkbox
}
class ColumnFactory {
constructor() {
this.specColumn = {
__default: 'Column',
__sequence: 'Sequence',
__checkbox: 'Checkbox'
};
}
create(name) {
let className = this.specColumn[name] ? this.specColumn[name] : this.specColumn['__default'];
return new classes[className](name); // this line no longer throw error
}
}
let factory = new ColumnFactory();
let column = factory.create('userName');
export {ColumnFactory, Column, Sequence, Checkbox};

For those of you that are not using ES6 and want to know how you can create classes by using a string here is what I have done to get this to work.
"use strict";
class Person {
constructor(x, y) {
this.x = x;
this.y = y;
}
}
window.classes = {};
window.classes.Person = Person;
document.body.innerText = JSON.stringify(new window.classes["Person"](1, 2));
As you can see the easiest way to do this is to add the class to an object.
Here is the fiddle:
https://jsfiddle.net/zxg7dsng/1/
Here is an example project that uses this approach:
https://github.com/pdxjohnny/dist-rts-client-web

I prefer this method:
allThemClasses.js
export class A {}
export class B {}
export class C {}
script.js
import * as Classes from './allThemClasses';
const a = new Classes['A'];
const b = new Classes['B'];
const c = new Classes['C'];

I know this is an old post, but recently I've had the same question about how to instance a class dynamically
I'm using webpack so following the documentation there is a way to load a module dynamically using the import() function
js/classes/MyClass.js
class MyClass {
test = null;
constructor(param) {
console.log(param)
this.test = param;
}
}
js/app.js
var p = "example";
var className = "MyClass";
import('./classes/'+className).then(function(mod) {
let myClass = new mod[className](p);
console.log(myClass);
}, function(failMsg) {
console.error("Fail to load class"+className);
console.error(failMsg);
});
Beware: this method is asynchronous and I can't really tell the performance cost for it,
But it works perfectly on my simple program (worth a try ^^)
Ps: To be fare I'm new to Es6 (a couple of days) I'm more a C++ / PHP / Java developer.
I hope this helps anyone that come across this question and that is it not a bad practice ^^".

Clarification
There are similar questions to this, including this SO question that was closed, that are looking for proxy classes or factory functions in JavaScript; also called dynamic classes. This answer is a modern solution in case you landed on this answer looking for any of those things.
Answer / Solution
As of 2022 I think there is a more elegant solution for use in the browser. I made a class called Classes that self-registers the property Class (uppercase C) on the window; code below examples.
Now you can have classes that you want to be able to reference dynamically register themselves globally:
// Make a class:
class Handler {
handleIt() {
// Handling it...
}
}
// Have it register itself globally:
Class.add(Handler);
// OR if you want to be a little more clear:
window.Class.add(Handler);
Then later on in your code all you need is the name of the class you would like to get its original reference:
// Get class
const handler = Class.get('Handler');
// Instantiate class for use
const muscleMan = new (handler)();
Or, even easier, just instantiate it right away:
// Directly instantiate class for use
const muscleMan = Class.new('Handler', ...args);
Code
You can see the latest code on my gist. Add this script before all other scripts and all of your classes will be able to register with it.
/**
* Adds a global constant class that ES6 classes can register themselves with.
* This is useful for referencing dynamically named classes and instances
* where you may need to instantiate different extended classes.
*
* NOTE: This script should be called as soon as possible, preferably before all
* other scripts on a page.
*
* #class Classes
*/
class Classes {
#classes = {};
constructor() {
/**
* JavaScript Class' natively return themselves, we can take advantage
* of this to prevent duplicate setup calls from overwriting the global
* reference to this class.
*
* We need to do this since we are explicitly trying to keep a global
* reference on window. If we did not do this a developer could accidentally
* assign to window.Class again overwriting any classes previously registered.
*/
if (window.Class) {
// eslint-disable-next-line no-constructor-return
return window.Class;
}
// eslint-disable-next-line no-constructor-return
return this;
}
/**
* Add a class to the global constant.
*
* #method
* #param {Class} ref The class to add.
* #return {boolean} True if ths class was successfully registered.
*/
add(ref) {
if (typeof ref !== 'function') {
return false;
}
this.#classes[ref.prototype.constructor.name] = ref;
return true;
}
/**
* Checks if a class exists by name.
*
* #method
* #param {string} name The name of the class you would like to check.
* #return {boolean} True if this class exists, false otherwise.
*/
exists(name) {
if (this.#classes[name]) {
return true;
}
return false;
}
/**
* Retrieve a class by name.
*
* #method
* #param {string} name The name of the class you would like to retrieve.
* #return {Class|undefined} The class asked for or undefined if it was not found.
*/
get(name) {
return this.#classes[name];
}
/**
* Instantiate a new instance of a class by reference or name.
*
* #method
* #param {Class|name} name A reference to the class or the classes name.
* #param {...any} args Any arguments to pass to the classes constructor.
* #returns A new instance of the class otherwise an error is thrown.
* #throws {ReferenceError} If the class is not defined.
*/
new(name, ...args) {
// In case the dev passed the actual class reference.
if (typeof name === 'function') {
// eslint-disable-next-line new-cap
return new (name)(...args);
}
if (this.exists(name)) {
return new (this.#classes[name])(...args);
}
throw new ReferenceError(`${name} is not defined`);
}
/**
* An alias for the add method.
*
* #method
* #alias Classes.add
*/
register(ref) {
return this.add(ref);
}
}
/**
* Insure that Classes is available in the global scope as Class so other classes
* that wish to take advantage of Classes can rely on it being present.
*
* NOTE: This does not violate https://www.w3schools.com/js/js_reserved.asp
*/
const Class = new Classes();
window.Class = Class;

This is an old question but we can find three main approaches that are very clever and useful:
1. The Ugly
We can use eval to instantiate our class like this:
class Column {
constructor(c) {
this.c = c
console.log(`Column with ${this.c}`);
}
}
function instantiator(name, ...params) {
const c = eval(name)
return new c(...params)
}
const name = 'Column';
const column = instantiator(name, 'box')
console.log({column})
However, eval has a big caveat, if we don't sanitize and don't add some layers of security, then we will have a big security whole that can be expose.
2. The Good
If we know the class names that we will use, then we can create a lookup table like this:
class Column {
constructor(c) {
console.log(`Column with ${c}`)
}
}
class Sequence {
constructor(a, b) {
console.log(`Sequence with ${a} and ${b}`)
}
}
class Checkbox {
constructor(c) {
console.log(`Checkbox with ${c}`)
}
}
// construct dict object that contains our mapping between strings and classes
const classMap = new Map([
['Column', Column],
['Sequence', Sequence],
['Checkbox', Checkbox],
])
function instantiator(name, ...p) {
return new(classMap.get(name))(...p)
}
// make a class from a string
let object = instantiator('Column', 'box')
object = instantiator('Sequence', 'box', 'index')
object = instantiator('Checkbox', 'box')
3. The Pattern
Finally, we can just create a Factory class that will safety handle the allowed classes and throw an error if it can load it.
class Column {
constructor(c) {
console.log(`Column with ${c}`)
}
}
class Sequence {
constructor(a, b) {
console.log(`Sequence with ${a} and ${b}`)
}
}
class Checkbox {
constructor(c) {
console.log(`Checkbox with ${c}`)
}
}
class ClassFactory {
static class(name) {
switch (name) {
case 'Column':
return Column
case 'Sequence':
return Sequence
case 'Checkbox':
return Checkbox
default:
throw new Error(`Could not instantiate ${name}`);
}
}
static create(name, ...p) {
return new(ClassFactory.class(name))(...p)
}
}
// make a class from a string
let object
object = ClassFactory.create('Column', 'box')
object = ClassFactory.create('Sequence', 'box', 'index')
object = ClassFactory.create('Checkbox', 'box')
I recommend The Good method. It is is clean and safe. Also, it should be better than using global or window object:
class definitions in ES6 are not automatically put on the global object like they would with other top level variable declarations (JavaScript trying to avoid adding more junk on top of prior design mistakes).
Therefore we will not pollute the global object because we are using a local classMap object to lookup the required class.

Related

JSDoc ts annotations for generic-ish base class (VS Code)

I'm trying to use JSDoc annotations for a base class where a constructor param (and some getter/method return vals) will be the classes which extend the base class.
Is there a way to specify that #template must be derived from base class?
For context I'm writing an abstraction for file system (files + folders) where their name on disk may contain #tags - the bulk of code is the same, hence desire for base class, but there are some subtle differences depending on whether it's file or folder (eg. for files the .ext is after the #tags thus a file name needs slightly different parsing to a folder name). Additionally, there will be classes derived from the file/folder classes, eg. certain parts of the folder tree merit contextual helper methods.
/**
* #template TParent the parent item, or `null` if root item
* #type Base<TParent?, string, boolean>
*/
class Base {
/** #type {TParent?} parent item or `null` if root
#parent
/** #type {Set<string>} list of tags from parsed item name
#tags
/** #type {boolean} `true` if this item represents a file
#isFile
/**
* #constructor
* #param {TParent?} parent - the parent item, or `null` if root
* etc...
*/
constructor(parent, name, isFile = false) {
this.#parent = parent
this.#isFile = isFile
this.#tags = this.#parse(name)
}
/** #returns {TParent} parent item, or `null` if root
get parent() { return this.#parent }
get tags() { return this.#tags }
#parse(name) { return new Set() }
methodActsOnParent() {
const goo = this.parent?.tags.has("#Goo") ?? false
// ... ^ TParent might not have .tags
}
}
class Folder extends Base {
// folder specific stuff
constructor(...) { ... }
}
class File extends Base {
// file specific stuff
constructor(...) { ... }
}
// in some external file:
class Foo extends Folder {
// foo specific stuff
constructor(...) { ... }
/** #returns {TParent} */
get parent() { return this.parent }
}
Is there some way to say " TParent must be derived from Base " so the code hints, etc., know that the base properties/methods/etc will be available?
And, if I know class Foo will always have a parent (it's never root), is there some way to specify that, given that Base class allows null for parent? That way I could reduce null checking without resorting to // #ts-ignore cheats.
You can set a constraint on generic type by #template {boolean} T. This is the equivalent of TypeScript Foo<T extends boolean>
/**
* #template {Base<any> | null} T
*/
class Base {
/**#type {T}*/
#parent;
constructor(/**#type {T}*/parent) {
this.#parent = parent;
}
get parent() { return this.#parent; }
methodActsOnParent() {
this.parent.methodActsOnParent();
}
}
/**
* #template {Base<any> | null} T
* #extends {Base<T>}
*/
class Foo extends Base {
}
const b = new Base(new Base(null));
b.parent.parent;
b.parent.parent.methodActsOnParent();
const f = new Foo(new Foo(null));
f.parent.parent;
f.parent.parent.methodActsOnParent();

Google Closure compiler: class as function argument

I can't figure out how to send class references as function arguments, so that Google Closure compiler will compile it right in advanced mode. In the below example I send a class reference to the Test1.doSomething function, which invokes a static function on the class. I've set the #param to Function, because I guess it still is a function, although I use ES6 style classes. Is there some other keyword I should use?
The below code works fine uncompiled, and also works fine if I go old school and change "class Test2" into "function Test2()". I've also tried to change the #param into function(), function(new:Test2), function(this:Test2), typeof Test2, etc, with no success.
class Test1
{
/**
* #param {!Function} typeRef
*/
doSomething(typeRef)
{
typeRef.doSomething();
}
}
class Test2
{
}
Test2.doSomething = function()
{
alert(1);
}
var t1 = new Test1();
window["t1"] = t1;
t1.doSomething(Test2);
I compile as follows:
--debug --formatting=PRETTY_PRINT --language_in ECMASCRIPT6_STRICT --language_out ECMASCRIPT6_STRICT --compilation_level ADVANCED_OPTIMIZATIONS --js Test.js --js_output_file Script.js
The result with "class Test2"
'use strict';
class $Test1$$ {
}
window.t1 = new $Test1$$;
The result with "function Test2()"
'use strict';
class $Test1$$ {
$doSomething$($typeRef$$) {
$typeRef$$.$doSomething$();
}
}
function $Test2$$() {
}
$Test2$$.$doSomething$ = function $$Test2$$$$doSomething$$() {
alert(1);
};
var $t1$$ = new $Test1$$;
window.t1 = $t1$$;
$t1$$.$doSomething$($Test2$$);
EDIT ON 29 JAN: Added a more complete example of what I'm trying to do:
class Car
{
constructor()
{
this.make = "Ford";
console.log("Car created.");
}
}
Car.create = function(registry)
{
let obj = new Car();
registry.add(obj);
return obj;
}
class Motorcycle
{
constructor()
{
this.make = "BMW";
console.log("Motorcycle created.");
}
}
Motorcycle.create = function(registry)
{
let obj = new Motorcycle();
registry.add(obj);
return obj;
}
class Registry
{
constructor()
{
this.vehicles = [];
}
add(vehicle)
{
this.vehicles.push(vehicle);
}
makeVehicle(typeRef)
{
typeRef.create(this);
}
}
function init()
{
var registry = new Registry();
registry.makeVehicle(Car);
registry.makeVehicle(Motorcycle);
registry.makeVehicle(Motorcycle);
registry.makeVehicle(Car);
registry.makeVehicle(Car);
}
To pass in a class, you can use the {typeof namespace} type:
#param {typeof Test2} TypeRef
As in your code:
class Test1
{
/**
* #param {typeof Test2} TypeRef
*/
doSomething(TypeRef)
{
(new TypeRef()).doSomething();
}
}
class Test2
{
doSomething ()
{
alert(1);
}
}
var t1 = new Test1();
window["t1"] = t1;
t1.doSomething(Test2);
Demo (don't forget to hit Compile)
When using #param (without typeof) you would pass an instance of that type*. This is far more common than passing a class itself
*: Sorry if this is not relevant but I noticed a couple of issues in the original code and wanted to show you how to fix them, even if not relevant;
To produce an instance-typed version, I had to change 3 things;
1) The Test2.doSomething assignment should be assigning to Test2.prototype;
Test2.prototype.doSomething = function()
{
alert(1);
}
Or using the ES6 Class format:
Test2
{
doSomething()
{
alert(1);
}
}
2) And to resolve a warning Test1.doSomething's #param annotation had to become Type2, as Function does not declare a Function.doSomething method.
/**
* #param {!Type2} typeRef
*/
3) The final line passes the class itself, not an instance of the class, so you should use the new operator to send in an instance of that class.
t1.doSomething(new Test2());
Cheers.

Split a Javascript class (ES6) over multiple files?

I have a Javascript class (in ES6) that is getting quite long. To organize it better I'd like to split it over 2 or 3 different files. How can I do that?
Currently it looks like this in a single file:
class foo extends bar {
constructor(a, b) {} // Put in file 1
methodA(a, b) {} // Put in file 1
methodB(a, b) {} // Put in file 2
methodC(a, b) {} // Put in file 2
}
Thanks!
When you create a class
class Foo extends Bar {
constructor(a, b) {
}
}
you can later add methods to this class by assigning to its prototype:
// methodA(a, b) in class Foo
Foo.prototype.methodA = function(a, b) {
// do whatever...
}
You can also add static methods similarly by assigning directly to the class:
// static staticMethod(a, b) in class Foo
Foo.staticMethod = function(a, b) {
// do whatever...
}
You can put these functions in different files, as long as they run after the class has been declared.
However, the constructor must always be part of the class declaration (you cannot move that to another file). Also, you need to make sure that the files where the class methods are defined are run before they are used.
Here's my solution. It:
uses regular modern classes and .bind()ing, no prototype. (EDIT: Actually, see the comments for more on this, it may not be desirable.)
works with modules. (I'll show an alternative option if you don't use modules.)
supports easy conversion from existing code.
yields no concern for function order (if you do it right).
yields easy to read code.
is low maintenance.
unfortunately does not play well with static functions in the same class, you'll need to split those off.
First, place this in a globals file or as the first <script> tag etc.:
BindToClass(functionsObject, thisClass) {
for (let [ functionKey, functionValue ] of Object.entries(functionsObject)) {
thisClass[functionKey] = functionValue.bind(thisClass);
}
}
This loops through an object and assigns and binds each function, in that object, by its name, to the class. It .bind()'s it for the this context, so it's like it was in the class to begin with.
Then extract your function(s) from your class into a separate file like:
//Use this if you're using NodeJS/Webpack. If you're using regular modules,
//use `export` or `export default` instead of `module.exports`.
//If you're not using modules at all, you'll need to map this to some global
//variable or singleton class/object.
module.exports = {
myFunction: function() {
//...
},
myOtherFunction: function() {
//...
}
};
Finally, require the separate file and call BindToClass like this in the constructor() {} function of the class, before any other code that might rely upon these split off functions:
//If not using modules, use your global variable or singleton class/object instead.
let splitFunctions = require('./SplitFunctions');
class MySplitClass {
constructor() {
BindToClass(splitFunctions, this);
}
}
Then the rest of your code remains the same as it would if those functions were in the class to begin with:
let msc = new MySplitClass();
msc.myFunction();
msc.myOtherFunction();
Likewise, since nothing happens until the functions are actually called, as long as BindToClass() is called first, there's no need to worry about function order. Each function, inside and outside of the class file, can still access any property or function within the class, as usual.
I choose to have all privte variables/functions in an object called private, and pass it as the first argument to the external functions.
this way they have access to the local variables/functions.
note that they have implicit access to 'this' as well
file: person.js
const { PersonGetAge, PersonSetAge } = require('./person_age_functions.js');
exports.Person = function () {
// use privates to store all private variables and functions
let privates={ }
// delegate getAge to PersonGetAge in an external file
// pass this,privates,args
this.getAge=function(...args) {
return PersonGetAge.apply(this,[privates].concat(args));
}
// delegate setAge to PersonSetAge in an external file
// pass this,privates,args
this.setAge=function(...args) {
return PersonSetAge.apply(this,[privates].concat(args));
}
}
file: person_age_functions.js
exports.PersonGetAge =function(privates)
{
// note: can use 'this' if requires
return privates.age;
}
exports.PersonSetAge =function(privates,age)
{
// note: can use 'this' if requires
privates.age=age;
}
file: main.js
const { Person } = require('./person.js');
let me = new Person();
me.setAge(17);
console.log(`I'm ${me.getAge()} years old`);
output:
I'm 17 years old
note that in order not to duplicate code on person.js, one can assign all functions in a loop.
e.g.
person.js option 2
const { PersonGetAge, PersonSetAge } = require('./person_age_functions.js');
exports.Person = function () {
// use privates to store all private variables and functions
let privates={ }
{
// assign all external functions
let funcMappings={
getAge:PersonGetAge,
setAge:PersonSetAge
};
for (const local of Object.keys(funcMappings))
{
this[local]=function(...args) {
return funcMappings[local].apply(this,[privates].concat(args));
}
}
}
}
You can add mixins to YourClass like this:
class YourClass {
ownProp = 'prop'
}
class Extension {
extendedMethod() {
return `extended ${this.ownProp}`
}
}
addMixins(YourClass, Extension /*, Extension2, Extension3 */)
console.log('Extended method:', (new YourClass()).extendedMethod())
function addMixins() {
var cls, mixin, arg
cls = arguments[0].prototype
for(arg = 1; arg < arguments.length; ++ arg) {
mixin = arguments[arg].prototype
Object.getOwnPropertyNames(mixin).forEach(prop => {
if (prop == 'constructor') return
if (Object.getOwnPropertyNames(cls).includes(prop))
throw(`Class ${cls.constructor.name} already has field ${prop}, can't mixin ${mixin.constructor.name}`)
cls[prop] = mixin[prop]
})
}
}
TypeScript Solution
foo-methods.ts
import { MyClass } from './class.js'
export function foo(this: MyClass) {
return 'foo'
}
bar-methods.ts
import { MyClass } from './class.js'
export function bar(this: MyClass) {
return 'bar'
}
class.ts
import * as barMethods from './bar-methods.js'
import * as fooMethods from './foo-methods.js'
const myClassMethods = { ...barMethods, ...fooMethods }
class _MyClass {
baz: string
constructor(baz: string) {
this.baz = baz
Object.assign(this, myClassMethods);
}
}
export type MyClass = InstanceType<typeof _MyClass> &
typeof myClassMethods;
export const MyClass = _MyClass as unknown as {
new (
...args: ConstructorParameters<typeof _MyClass>
): MyClass;
};
My solution is similar to the one by Erez (declare methods in files and then assign methods to this in the constructor), but
it uses class syntax instead of declaring constructor as a function
no option for truly private fields - but this was not a concern for this question anyway
it does not have the layer with the .apply() call - functions are inserted into the instance directly
one method per file: this is what works for me, but the solution can be modified
results in more concise class declaration
1. Assign methods in constructor
C.js
class C {
constructor() {
this.x = 1;
this.addToX = require('./addToX');
this.incX = require('./incX');
}
}
addToX.js
function addToX(val) {
this.x += val;
return this.x;
}
module.exports = addToX;
incX.js
function incX() {
return this.addToX(1);
}
module.exports = incX;
2. Same, but with instance fields syntax
Note that this syntax is a Stage 3 proposal as of now.
But it works in Node.js 14 - the platform I care about.
C.js
class C {
x = 1;
addToX = require('./addToX');
incX = require('./incX');
}
Test
const c = new C();
console.log('c.incX()', c.incX());
console.log('c.incX()', c.incX());

JavaScript: how to create object from class if I have class name as string? [duplicate]

I want create object factory using ES6 but old-style syntax doesn't work with new.
I have next code:
export class Column {}
export class Sequence {}
export class Checkbox {}
export class ColumnFactory {
constructor() {
this.specColumn = {
__default: 'Column',
__sequence: 'Sequence',
__checkbox: 'Checkbox'
};
}
create(name) {
let className = this.specColumn[name] ? this.specColumn[name] : this.specColumn['__default'];
return new window[className](name); // this line throw error
}
}
let factory = new ColumnFactory();
let column = factory.create('userName');
What do I do wrong?
Don't put class names on that object. Put the classes themselves there, so that you don't have to rely on them being global and accessible (in browsers) through window.
Btw, there's no good reason to make this factory a class, you would probably only instantiate it once (singleton). Just make it an object:
export class Column {}
export class Sequence {}
export class Checkbox {}
export const columnFactory = {
specColumn: {
__default: Column, // <--
__sequence: Sequence, // <--
__checkbox: Checkbox // <--
},
create(name, ...args) {
let cls = this.specColumn[name] || this.specColumn.__default;
return new cls(...args);
}
};
There is a small & dirty way to do that:
function createClassByName(name,...a) {
var c = eval(name);
return new c(...a);
}
You can now create a class like that:
let c = createClassByName( 'Person', x, y );
The problem is that the classes are not properties of the window object. You can have an object with properties "pointing" to your classes instead:
class Column {}
class Sequence {}
class Checkbox {}
let classes = {
Column,
Sequence,
Checkbox
}
class ColumnFactory {
constructor() {
this.specColumn = {
__default: 'Column',
__sequence: 'Sequence',
__checkbox: 'Checkbox'
};
}
create(name) {
let className = this.specColumn[name] ? this.specColumn[name] : this.specColumn['__default'];
return new classes[className](name); // this line no longer throw error
}
}
let factory = new ColumnFactory();
let column = factory.create('userName');
export {ColumnFactory, Column, Sequence, Checkbox};
For those of you that are not using ES6 and want to know how you can create classes by using a string here is what I have done to get this to work.
"use strict";
class Person {
constructor(x, y) {
this.x = x;
this.y = y;
}
}
window.classes = {};
window.classes.Person = Person;
document.body.innerText = JSON.stringify(new window.classes["Person"](1, 2));
As you can see the easiest way to do this is to add the class to an object.
Here is the fiddle:
https://jsfiddle.net/zxg7dsng/1/
Here is an example project that uses this approach:
https://github.com/pdxjohnny/dist-rts-client-web
I prefer this method:
allThemClasses.js
export class A {}
export class B {}
export class C {}
script.js
import * as Classes from './allThemClasses';
const a = new Classes['A'];
const b = new Classes['B'];
const c = new Classes['C'];
I know this is an old post, but recently I've had the same question about how to instance a class dynamically
I'm using webpack so following the documentation there is a way to load a module dynamically using the import() function
js/classes/MyClass.js
class MyClass {
test = null;
constructor(param) {
console.log(param)
this.test = param;
}
}
js/app.js
var p = "example";
var className = "MyClass";
import('./classes/'+className).then(function(mod) {
let myClass = new mod[className](p);
console.log(myClass);
}, function(failMsg) {
console.error("Fail to load class"+className);
console.error(failMsg);
});
Beware: this method is asynchronous and I can't really tell the performance cost for it,
But it works perfectly on my simple program (worth a try ^^)
Ps: To be fare I'm new to Es6 (a couple of days) I'm more a C++ / PHP / Java developer.
I hope this helps anyone that come across this question and that is it not a bad practice ^^".
Clarification
There are similar questions to this, including this SO question that was closed, that are looking for proxy classes or factory functions in JavaScript; also called dynamic classes. This answer is a modern solution in case you landed on this answer looking for any of those things.
Answer / Solution
As of 2022 I think there is a more elegant solution for use in the browser. I made a class called Classes that self-registers the property Class (uppercase C) on the window; code below examples.
Now you can have classes that you want to be able to reference dynamically register themselves globally:
// Make a class:
class Handler {
handleIt() {
// Handling it...
}
}
// Have it register itself globally:
Class.add(Handler);
// OR if you want to be a little more clear:
window.Class.add(Handler);
Then later on in your code all you need is the name of the class you would like to get its original reference:
// Get class
const handler = Class.get('Handler');
// Instantiate class for use
const muscleMan = new (handler)();
Or, even easier, just instantiate it right away:
// Directly instantiate class for use
const muscleMan = Class.new('Handler', ...args);
Code
You can see the latest code on my gist. Add this script before all other scripts and all of your classes will be able to register with it.
/**
* Adds a global constant class that ES6 classes can register themselves with.
* This is useful for referencing dynamically named classes and instances
* where you may need to instantiate different extended classes.
*
* NOTE: This script should be called as soon as possible, preferably before all
* other scripts on a page.
*
* #class Classes
*/
class Classes {
#classes = {};
constructor() {
/**
* JavaScript Class' natively return themselves, we can take advantage
* of this to prevent duplicate setup calls from overwriting the global
* reference to this class.
*
* We need to do this since we are explicitly trying to keep a global
* reference on window. If we did not do this a developer could accidentally
* assign to window.Class again overwriting any classes previously registered.
*/
if (window.Class) {
// eslint-disable-next-line no-constructor-return
return window.Class;
}
// eslint-disable-next-line no-constructor-return
return this;
}
/**
* Add a class to the global constant.
*
* #method
* #param {Class} ref The class to add.
* #return {boolean} True if ths class was successfully registered.
*/
add(ref) {
if (typeof ref !== 'function') {
return false;
}
this.#classes[ref.prototype.constructor.name] = ref;
return true;
}
/**
* Checks if a class exists by name.
*
* #method
* #param {string} name The name of the class you would like to check.
* #return {boolean} True if this class exists, false otherwise.
*/
exists(name) {
if (this.#classes[name]) {
return true;
}
return false;
}
/**
* Retrieve a class by name.
*
* #method
* #param {string} name The name of the class you would like to retrieve.
* #return {Class|undefined} The class asked for or undefined if it was not found.
*/
get(name) {
return this.#classes[name];
}
/**
* Instantiate a new instance of a class by reference or name.
*
* #method
* #param {Class|name} name A reference to the class or the classes name.
* #param {...any} args Any arguments to pass to the classes constructor.
* #returns A new instance of the class otherwise an error is thrown.
* #throws {ReferenceError} If the class is not defined.
*/
new(name, ...args) {
// In case the dev passed the actual class reference.
if (typeof name === 'function') {
// eslint-disable-next-line new-cap
return new (name)(...args);
}
if (this.exists(name)) {
return new (this.#classes[name])(...args);
}
throw new ReferenceError(`${name} is not defined`);
}
/**
* An alias for the add method.
*
* #method
* #alias Classes.add
*/
register(ref) {
return this.add(ref);
}
}
/**
* Insure that Classes is available in the global scope as Class so other classes
* that wish to take advantage of Classes can rely on it being present.
*
* NOTE: This does not violate https://www.w3schools.com/js/js_reserved.asp
*/
const Class = new Classes();
window.Class = Class;
This is an old question but we can find three main approaches that are very clever and useful:
1. The Ugly
We can use eval to instantiate our class like this:
class Column {
constructor(c) {
this.c = c
console.log(`Column with ${this.c}`);
}
}
function instantiator(name, ...params) {
const c = eval(name)
return new c(...params)
}
const name = 'Column';
const column = instantiator(name, 'box')
console.log({column})
However, eval has a big caveat, if we don't sanitize and don't add some layers of security, then we will have a big security whole that can be expose.
2. The Good
If we know the class names that we will use, then we can create a lookup table like this:
class Column {
constructor(c) {
console.log(`Column with ${c}`)
}
}
class Sequence {
constructor(a, b) {
console.log(`Sequence with ${a} and ${b}`)
}
}
class Checkbox {
constructor(c) {
console.log(`Checkbox with ${c}`)
}
}
// construct dict object that contains our mapping between strings and classes
const classMap = new Map([
['Column', Column],
['Sequence', Sequence],
['Checkbox', Checkbox],
])
function instantiator(name, ...p) {
return new(classMap.get(name))(...p)
}
// make a class from a string
let object = instantiator('Column', 'box')
object = instantiator('Sequence', 'box', 'index')
object = instantiator('Checkbox', 'box')
3. The Pattern
Finally, we can just create a Factory class that will safety handle the allowed classes and throw an error if it can load it.
class Column {
constructor(c) {
console.log(`Column with ${c}`)
}
}
class Sequence {
constructor(a, b) {
console.log(`Sequence with ${a} and ${b}`)
}
}
class Checkbox {
constructor(c) {
console.log(`Checkbox with ${c}`)
}
}
class ClassFactory {
static class(name) {
switch (name) {
case 'Column':
return Column
case 'Sequence':
return Sequence
case 'Checkbox':
return Checkbox
default:
throw new Error(`Could not instantiate ${name}`);
}
}
static create(name, ...p) {
return new(ClassFactory.class(name))(...p)
}
}
// make a class from a string
let object
object = ClassFactory.create('Column', 'box')
object = ClassFactory.create('Sequence', 'box', 'index')
object = ClassFactory.create('Checkbox', 'box')
I recommend The Good method. It is is clean and safe. Also, it should be better than using global or window object:
class definitions in ES6 are not automatically put on the global object like they would with other top level variable declarations (JavaScript trying to avoid adding more junk on top of prior design mistakes).
Therefore we will not pollute the global object because we are using a local classMap object to lookup the required class.

Declaring static constants in ES6 classes?

I want to implement constants in a class, because that's where it makes sense to locate them in the code.
So far, I have been implementing the following workaround with static methods:
class MyClass {
static constant1() { return 33; }
static constant2() { return 2; }
// ...
}
I know there is a possibility to fiddle with prototypes, but many recommend against this.
Is there a better way to implement constants in ES6 classes?
Here's a few things you could do:
Export a const from the module. Depending on your use case, you could just:
export const constant1 = 33;
And import that from the module where necessary. Or, building on your static method idea, you could declare a static get accessor:
const constant1 = 33,
constant2 = 2;
class Example {
static get constant1() {
return constant1;
}
static get constant2() {
return constant2;
}
}
That way, you won't need parenthesis:
const one = Example.constant1;
Babel REPL Example
Then, as you say, since a class is just syntactic sugar for a function you can just add a non-writable property like so:
class Example {
}
Object.defineProperty(Example, 'constant1', {
value: 33,
writable : false,
enumerable : true,
configurable : false
});
Example.constant1; // 33
Example.constant1 = 15; // TypeError
It may be nice if we could just do something like:
class Example {
static const constant1 = 33;
}
But unfortunately this class property syntax is only in an ES7 proposal, and even then it won't allow for adding const to the property.
class Whatever {
static get MyConst() { return 10; }
}
let a = Whatever.MyConst;
Seems to work for me.
I'm using babel and the following syntax is working for me:
class MyClass {
static constant1 = 33;
static constant2 = {
case1: 1,
case2: 2,
};
// ...
}
MyClass.constant1 === 33
MyClass.constant2.case1 === 1
Please consider that you need the preset "stage-0".
To install it:
npm install --save-dev babel-preset-stage-0
// in .babelrc
{
"presets": ["stage-0"]
}
Update for stage:
it was moved on stage-3.
Update Babel 7:
As per Babel 7 stage presets are deprecated.
The Babel plugin to use is #babel/plugin-proposal-class-properties.
npm i --save-dev #babel/plugin-proposal-class-properties
{
"plugins": ["#babel/plugin-proposal-class-properties"]
}
Note: This plugin is included in #babel/preset-env
In this document it states:
There is (intentionally) no direct declarative way to define either prototype data properties (other than methods) class properties, or instance property
This means that it is intentionally like this.
Maybe you can define a variable in the constructor?
constructor(){
this.key = value
}
It is also possible to use Object.freeze on you class(es6)/constructor function(es5) object to make it immutable:
class MyConstants {}
MyConstants.staticValue = 3;
MyConstants.staticMethod = function() {
return 4;
}
Object.freeze(MyConstants);
// after the freeze, any attempts of altering the MyConstants class will have no result
// (either trying to alter, add or delete a property)
MyConstants.staticValue === 3; // true
MyConstants.staticValue = 55; // will have no effect
MyConstants.staticValue === 3; // true
MyConstants.otherStaticValue = "other" // will have no effect
MyConstants.otherStaticValue === undefined // true
delete MyConstants.staticMethod // false
typeof(MyConstants.staticMethod) === "function" // true
Trying to alter the class will give you a soft-fail (won't throw any errors, it will simply have no effect).
Maybe just put all your constants in a frozen object?
class MyClass {
constructor() {
this.constants = Object.freeze({
constant1: 33,
constant2: 2,
});
}
static get constant1() {
return this.constants.constant1;
}
doThisAndThat() {
//...
let value = this.constants.constant2;
//...
}
}
You can create a way to define static constants on a class using an odd feature of ES6 classes. Since statics are inherited by their subclasses, you can do the following:
const withConsts = (map, BaseClass = Object) => {
class ConstClass extends BaseClass { }
Object.keys(map).forEach(key => {
Object.defineProperty(ConstClass, key, {
value: map[key],
writable : false,
enumerable : true,
configurable : false
});
});
return ConstClass;
};
class MyClass extends withConsts({ MY_CONST: 'this is defined' }) {
foo() {
console.log(MyClass.MY_CONST);
}
}
Like https://stackoverflow.com/users/2784136/rodrigo-botti said, I think you're looking for Object.freeze(). Here's an example of a class with immutable statics:
class User {
constructor(username, age) {
if (age < User.minimumAge) {
throw new Error('You are too young to be here!');
}
this.username = username;
this.age = age;
this.state = 'active';
}
}
User.minimumAge = 16;
User.validStates = ['active', 'inactive', 'archived'];
deepFreeze(User);
function deepFreeze(value) {
if (typeof value === 'object' && value !== null) {
Object.freeze(value);
Object.getOwnPropertyNames(value).forEach(property => {
deepFreeze(value[property]);
});
}
return value;
}
I did this.
class Circle
{
constuctor(radius)
{
this.radius = radius;
}
static get PI()
{
return 3.14159;
}
}
The value of PI is protected from being changed since it is a value being returned from a function. You can access it via Circle.PI. Any attempt to assign to it is simply dropped on the floor in a manner similar to an attempt to assign to a string character via [].
You could use import * as syntax. Although not a class, they are real const variables.
Constants.js
export const factor = 3;
export const pi = 3.141592;
index.js
import * as Constants from 'Constants.js'
console.log( Constants.factor );
You can make the "constants" read-only (immutable) by freezing the class. e.g.
class Foo {
static BAR = "bat"; //public static read-only
}
Object.freeze(Foo);
/*
Uncaught TypeError: Cannot assign to read only property 'BAR' of function 'class Foo {
static BAR = "bat"; //public static read-only
}'
*/
Foo.BAR = "wut";
Here is one more way you can do
/*
one more way of declaring constants in a class,
Note - the constants have to be declared after the class is defined
*/
class Auto{
//other methods
}
Auto.CONSTANT1 = "const1";
Auto.CONSTANT2 = "const2";
console.log(Auto.CONSTANT1)
console.log(Auto.CONSTANT2);
Note - the Order is important, you cannot have the constants above
Usage
console.log(Auto.CONSTANT1);
The cleanest way I've found of doing this is with TypeScript - see How to implement class constants?
class MyClass {
static readonly CONST1: string = "one";
static readonly CONST2: string = "two";
static readonly CONST3: string = "three";
}
Just declare your variables as private and use a get method to retrieve them.
class MyClass {
#myConst = 'Something';
static #anotherConst = 'Something Else';
get myConst() {
return this.#myConst; // instance method
}
static get anotherConst() {
return MyClass.#anotherConst; // static method
}
}
let myClass = new MyClass();
console.log( myClass.myConst + ' is not ' + MyClass.anotherConst );
Users cannot change the original variable, and you can write the class to use the get methods rather than the private variables themselves.
One pattern that I use to expose error codes, i.e.,
I have many constants inside the module
I may not want to expose all constants to callers
I do not want to provide 1 static constant for one exposed constant
// inside the module
const Errors = {
INTERNAL: 100,
EMPTY_QUEUE: 101,
UNKNOWN_COMMAND: 102,
OK: 200,
MOVE: 201,
CREATE_DOT: 202,
PIXEL_MAPPING: 203
}
Object.freeze(Errors);
class PlotterError extends Error {
// use constant inside the module
code = Errors.INTERNAL;
constructor(message, code) {
super(message);
this.name = 'PlotterError';
this.code = code
}
}
// expose via static constant
Class Plotter {
.....
static get ERRORS() {
return Errors;
}
....
export Plotter;
// module ends
// in the caller
import {Plotter} from ...
try {
this.plotter.execute();
} catch(error) {
if(error.code == Plotter.ERRORS.EMPTY_QUEUE) {
//
}
}
We can also decide to expose only the constants we want by breaking the constants acr two objects.
If you are comfortable mixing and matching between function and class syntax you can declare constants after the class (the constants are 'lifted') . Note that Visual Studio Code will struggle to auto-format the mixed syntax, (though it works).
class MyClass {
// ...
}
MyClass.prototype.consts = {
constant1: 33,
constant2: 32
};
mc = new MyClass();
console.log(mc.consts.constant2);
Adding up to other answers you need to export the class to use in a different class. This is a typescript version of it.
//Constants.tsx
const DEBUG: boolean = true;
export class Constants {
static get DEBUG(): boolean {
return DEBUG;
}
}
//Anotherclass.tsx
import { Constants } from "Constants";
if (Constants.DEBUG) {
console.log("debug mode")
}
If trying to make a const/variable static to a class; try using the hash (#) to define a place holder, than a function to access it.
class Region {
// initially empty, not accessible from outside
static #empty_region = null;
/*
Make it visible to the outside and unchangeable
[note] created on first call to getter.
*/
static EMPTY() {
if (!this.#empty_region)
this.#empty_region = new Region(0, 0, 0, 0);
return this.#empty_region;
}
#reg = {x0:0, y0:0, x1:0, y1:0};
constructor(x0, y0, x1, y1) {
this.setRegion(x0, y0, x1, y1);
}
// setters/getters
}
Implementation:
let someRegion = Region.EMPTY();
let anotherRegion = Region.EMPTY();
Here You Go!
const Status = Object.freeze(class Status {
static Disabled = 0
static Live = 1
})

Categories

Resources