90

Here's what I'm trying to do -- this is pseudo code and doesn't work. Does anyone know how to accomplish this for real:

// Define the class
MyClass = Class.extend({});

// Store the class name in a string
var classNameString = 'MyClass';

// Instantiate the object using the class name string
var myObject = new classNameString();

12 Answers 12

73

Would it work if you did something like this:

var myObject = window[classNameString];

..?

Sign up to request clarification or add additional context in comments.

13 Comments

Think you meant window[classNameString] (no quotes). Breaks as soon as MyClass is moved to a lower (i.e. function) scope. @kirk: yes this is cross-browser.
How do you pass arguments to the constructor using this method?
@JamesMcMahon In that case you'd have to use window[classNameString](args). But as Crescent Fresh mentions, be careful, as this might break in some cases.
This doesn't seem to be working for me. After some trial and error I found that this works: var obj = new Home(id);, but then this doesn't: var obj = new window["Home"](id);. I'm trying to get this to work: new window[x](id); where x = "Home"... The error I am getting is Uncaught TypeError: window[x] is not a constructor
|
59

Here's a more robust solution that will work with namespaced functions:

var stringToFunction = function(str) {
  var arr = str.split(".");

  var fn = (window || this);
  for (var i = 0, len = arr.length; i < len; i++) {
    fn = fn[arr[i]];
  }

  if (typeof fn !== "function") {
    throw new Error("function not found");
  }

  return  fn;
};

Example:

my = {};
my.namespaced = {};
(my.namespaced.MyClass = function() {
  console.log("constructed");
}).prototype = {
  do: function() {
    console.log("doing");
  }
};

var MyClass = stringToFunction("my.namespaced.MyClass");
var instance = new MyClass();
instance.do();

5 Comments

Why (windows || this), isn't window always going to be defined?
@JamesMcMahon: The world as we know is no longer the reality. Tenants like nodejs have also come to occupy our planet! :)
Since strict mode has come along (ECMA-262 ed 5 in 2009), window || this may return undefined in a non-browser environment if this isn't set by the call (which it isn't in the example).
Hi how to use this and make it work for classes that are imported?? e.g. import Grid from '../../mazes/components/Grid'
@preston: this unfortunately won't work with es6 classes, because it's searching for the class constructor function on either window or this.
38

BTW: window is the reference to the global Object in browser JavaScript. Which is also this, and should work even in non-browser environments such as Node.js, Chrome extensions, transpiled code etc.

var obj = new this[classNameString]();

The limitation is that the class being called must be in the global context. If you want to apply the same to a scoped class you need to do:

var obj = (Function('return new ' + classNameString))()

However, there really is no reason to use a string. JavaScript functions are themselves objects, just like strings which are objects also.

Edit

Here is a better way to get the global scope that works in strict mode as well as non-browser JS environments:

var global;
try {
  global = Function('return this')() || (42, eval)('this');
} catch(e) {
  global = window;
}

// and then
var obj = new global[classNameString]

From: How to get the global object in JavaScript?

8 Comments

It's only this if called from a global context. window works regardless of the context you are in, so I see no reason to prefer this over window.
Like the answer states, the environment you are in is not necessarily a browser. Therefore, using this is preferable, because in a non-browser environment window may be undefined, or not what you expect.
@XedinUnknown Ususally when you write a piece of javascript you know if it's for clientside or let's say for node. If you know it's for a browser, then there is no disadvantages to prefer window over this.
@Sebi, nowhere in the question is it implied that the environment is clientside. Also, clientside doesn't necessarily mean "browser" either. The answers are providing a way to fulfill the OP's requirements by explicitly referencing the global context - that's the whole point. I'm pointing out that the only reliable way to reference the global context from the global context is this, not window. Furthermore, the most reliable way to reference the global context NOT from the global context is top. Also, maybe good to inject it into the closure.
@devios1 If you test the code you'll notice it works from any context not just the global context. That's why we do Function('return this')() instead of return this. The former automatically switches to the global context. This works regardless of context. window can be overwritten, and is browser only. It's good to not know code execution context to write cross platform code. The answer I gave is cross platform and cannot be overwritten and therefore much better than using window. It will work in transpiled code, like with webpack, gulp and in node, extensions etc. Please test it first.
|
14

If MyClass is global, you can access it as a property of window object (assuming your code runs in a browser) using subscript notation.

var myObject = new window["MyClass"]();

2 Comments

Does this work in all browsers? I see some posts complaining that it doesn't work for them.
This doesn't work with es6 classes, because unlike global variables and global functions defined with var or function, class doesn't add it to window / the global this. You may be able to get it to work by explicitly setting window["MyClass"] = MyClass; in the class file (or creating a context object on window such as window.classes).
11

If classNameString come from secure source you can use

var classNameString = 'MyClass';
var myObject = eval("new " + classNameString + "()");

This solution works with namespaces and is independent on platform (browser/server).

10 Comments

I don't know about it and don't know why it was deleted?
@Jacksonkr, does that also mean I shouldn't drink a beer in the first place because it may become a bad habit? :)
Per your analogy: Using eval when you've heard it's a bad idea is like driving drunk. "Nothing happened ...hic... last time I drove drink ..hic.. in fact I drive better drunk! ...hic..." By the time something goes wrong it's too late. Don't be that guy.
To advocate NEVER using a function, eval or otherwise, is just bad advice. If it was a function without any use cases, it would've been depreciated and removed as a feature by now.
Exactly MacroMan. For it to be deleted as an answer is ridiculous.
|
11

Browser global object is window and whenever you define global variables with var or functions with function, you are adding them in window. Thus you can get your "class" definition there:

var args = [];
var className = 'MyClass';
var obj = new window[className](args);

But this won't work for ES6 class declarations

Classes declared using ES6 keyword class are per-standard treated differently.

A class declared with class MyClass { } defines a global class that does not become a property of window global object. In other words the following applies

class MyClass {};
typeof window.MyClass === 'undefined';

So, how to do the same with ES6 classes? Object access notation is required because is what is needed to parse the string name, but parent object to search in is no longer available.

One way is to create your own context object, declare there your class and search for it there. In code:

// this variable actually goes in `window`
var classes = {};
// declare class inside
classes.MyClass = class {
   // the class code
};

var args = [];
var className = 'MyClass';
var obj = new classes[className](args); // dynamic for "new classes.MyClass(args)"

3 Comments

I really like this method, as in my application this is exactly what I need. I do have an issue though, it's kinda on, but also off-topic. How would you implement this when you define your classes in separate files? Beforehand I could import all these classes seperately, but now I'm not quite sure how to 'run' my class file. So that it appends this class to the list of available classes (I hope this makes sense). As my main.js does not see the classes (obviously, because for my main.js the classes are never defined). I'm making a application that runs in chrome btw.
@Jelmer What I would do is this: still import those files. At the top of each file, check if var classes has been defined, and if not, define it: if ( !classes ) { var classes = {}; }. Add the class definition the normal way: class MyClass {} Then, add the class to the classes global object: classes["MyClass"] = MyClass; Because you imported the files, the classes are defined to use as new MyClass, and because you created the classes global variable, you can get it from classes["MyClass"] or classes.MyClass.
@Jelmer or you could use something like globalThis.
7
function myClass(arg){
}

var str="myClass";
dynamic_class=eval(str);

var instance=new dynamic_class(arg); // OK

Edit: inline example

function Person(name){
    this.name=name;
}
var person1=new (eval("Person"))("joe");

3 Comments

This works, but using eval() (and especially direct eval()) is recommended against: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…!
I'm currently looking for ways to refactor code using this same methodology, because of the security risks this introduces. Don't set yourself up to have to do this. Alternatives which include using str to get the class constructor from an object you create (e.g. var instance = new listOfClasses[str], as others here have proposed) are much safer.
FWIW, if you're going to do this anyway, p l e a s e just make sure you aren't getting the contents of str from user input or the DOM. At that point, you might as well just eval("hackMePlz()");...
6

This works:

import { Class1 } from './profiles/class1.js'
import { Class2 } from './profiles/class2.js'

let profiles = {
  Class1,
  Class2
}

let profileAsString = 'Class1'
new profiles[profileAsString]()

Comments

3

Here is improved version of Yuriy's method that also handles objects.

var stringToObject = function(str, type) {
    type = type || "object";  // can pass "function"
    var arr = str.split(".");

    var fn = (window || this);
    for (var i = 0, len = arr.length; i < len; i++) {
        fn = fn[arr[i]];
    }
    if (typeof fn !== type) {
        throw new Error(type +" not found: " + str);
    }

    return  fn;
};

Comments

2

In my case I was preloading data from my server and attempting to load the preloaded data using classes (in particular, I am using vuex-orm). Instead of anything super fancy, I opted for a list of models that I expect, and mapped them to classes that I had already imported at the top of the file. Observe:

import Video from '@models/Video'
import Purchase from '@models/Purchase'

let modelClassMap = {
    'Video': Video,
    'Purchase': Purchase,
}

Object.entries(preload.models).forEach(entry => {
    const [modelName, modelData] = entry
    if(modelClassMap[modelName]){
        modelClassMap[modelName].insertOrUpdate({data: modelData})
    }
})

Explicit, secure, simple. Nice!

Comments

0

myOption = {.......}

new (eval( varNameClass ))({ param0:'xxx', myThis: this, option: myOption })

Comments

-1

On Firefox, there are security rules for extensions, and so for the Javascript console. Don't make the mistake I did to test in the console because none of those solutions work. Whereas when you test from a page it works better :

  • the eval solution works well
  • Function('return new '... works (object is created) except for the constructor's arguments that are passed as "undefined" I didn't test other solutions.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.