1

I have multiple similar classes that I would like to call depending on the situation. I have a variable that will contain the name of the class I want to call.

So for example, I have this JavaScript code defining my classes.

class Parent {
    constructor(id) {
        // Do stuff...
    }
}

class Child1 extends Parent {};

class Child2 extends Parent {}; // and many, many more...

Then I have this code which makes a new Child1 object.

var obj = new Child1(id);

However, I would like to be able to have the name of the class to call in a variable, and then call it with that, like so:

var className = "Child1"; //Or child 2, depending on scenario.
var obj = new className(id);

I know in PHP I can do $obj = new $className(id);, which is exactly what I would like to do here in JavaScript.

I have read a few answers to some questions (this one for example) that said I could do this:

var obj = new window[className](id);

But doing that returned this error:

Uncaught TypeError: window[className] is not a constructor

Originally I had defined var className = "Child1" within a function, and I thought the problem was that className wasn't global, but when I tried to define className globally, it still didn't work...

lleaff
  • 4,249
  • 17
  • 23

2 Answers2

1

classes do not become properties of the global object, so window[...] won't work.

You have to build your own mapping:

// Can also use `Map` instead
const classes = {
  Child1: Child1,
  // or [Child1.name]: Child1 which can be useful if you want to build this dynamically
};

and then look the class up via

classes[className]

Related: "Variable" variables in Javascript?

Felix Kling
  • 795,719
  • 175
  • 1,089
  • 1,143
1

If you want to keep it as concise as you'd want, this is a case where you need to harness the evil powers of eval, so that your code works whether it's invoked in the global scope or not:

class ClassA {}
class ClassB {}

const classToInstantiate = 'ClassB';

const myInstance = eval(`new ${classToInstantiate}()`);

console.log(myInstance instanceof ClassB);

Although having to resort to this kind of trick is definitively a code smell, and a possible huge security issue if the value of the string variable is in any way dependent on user input.

The mapping option is what you'd most likely want to do as even if it adds some boilerplate, it's more reliable and you can more easily handle the case where the string doesn't contain a valid class name, e.g.:

class ClassA {}
class ClassB {}

const classNamesMap = {
  ClassA,
  ClassB,
};

function getClass(className) {
  if (!className in classNamesMap) {
    console.error('Invalid class name');
    return null;
  }
  return classNamesMap[className];
}

const myInstance = new (getClass('ClassB'))();

console.log(myInstance instanceof ClassB);
lleaff
  • 4,249
  • 17
  • 23