88

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();
mikemaccana
  • 110,530
  • 99
  • 389
  • 494
Kirk Ouimet
  • 27,280
  • 43
  • 127
  • 177

12 Answers12

74

Would it work if you did something like this:

var myObject = window[classNameString];

..?

peirix
  • 36,512
  • 23
  • 96
  • 126
  • That worked. peirix you are awesome. You think it's cross-browser compatible? – Kirk Ouimet Sep 02 '09 at 06:39
  • 1
    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. – Crescent Fresh Sep 02 '09 at 07:17
  • 1
    How do you pass arguments to the constructor using this method? – James McMahon Sep 04 '12 at 20:17
  • 3
    @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. – peirix Sep 05 '12 at 08:47
  • @peirix That's great! Any idea if I can do that with classes loaded with requirejs?? – Renaud Feb 13 '13 at 15:21
  • @Reno I don't know. I haven't tried, but that should be easy to try out. I would guess that there would be no problems, since it's basically just getting variables/classes from a global level. – peirix Feb 14 '13 at 07:30
  • @peirix I got it working, it goes like this : `require([myModuleName], function(myModule) { var myModuleInstance = new myModule(); });` where `myModuleName` is the string containing the name of the class I want to instantiate. – Renaud Feb 14 '13 at 17:29
  • @Mike That's probably because you don't have the global `window` object in node.js. And, that wasn't really part of the question either. I think it should work with the `global` object in node.js instead of `window` https://nodejs.org/api/globals.html – peirix May 13 '15 at 11:59
  • Doesn't seem to work in node.js with global either. Not sure why., – Mike May 14 '15 at 14:25
  • @Mike Probably better to start a new question on it, rather than discussing it here (: – peirix May 18 '15 at 11:47
  • 12
    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` – Abraham Murciano Benzadon Jul 12 '17 at 18:05
  • not working in case of `window[prop.pipeVal]('en-US')` – Pardeep Jain May 08 '18 at 10:03
  • 2
    Warning: this [won't work for ES6 classes](https://stackoverflow.com/a/68016983/214517). – Niki Romagnoli Jun 17 '21 at 23:22
58

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();
Yuriy Nemtsov
  • 3,869
  • 4
  • 30
  • 44
  • Why `(windows || this)`, isn't window always going to be defined? – James McMahon Sep 06 '12 at 15:54
  • 12
    @JamesMcMahon: The world as we know is no longer the reality. Tenants like nodejs have also come to occupy our planet! :) – Mrchief May 06 '13 at 20:33
  • Since strict mode has come along ([*ECMA-262 ed 5*](http://www.ecma-international.org/publications/files/ECMA-ST-ARCH/ECMA-262%205th%20edition%20December%202009.pdf) 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). – RobG Jun 01 '17 at 23:13
  • Hi how to use this and make it work for classes that are imported?? e.g. import Grid from '../../mazes/components/Grid' – preston Sep 12 '19 at 03:46
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?

Community
  • 1
  • 1
bucabay
  • 5,235
  • 2
  • 26
  • 37
  • 12
    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`. – devios1 Oct 18 '11 at 16:55
  • 6
    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 Jan 03 '16 at 17:27
  • @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 Apr 12 '16 at 18:01
  • 2
    @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. – XedinUnknown Apr 14 '16 at 15:21
  • 1
    @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. – bucabay Apr 25 '17 at 12:16
  • @devios1 I think the confusion here is that the function must be in the global scope. The calling context can be any scope. Which is the same for using `window` without the issues with `window`. The calling context can be any scope. – bucabay Apr 25 '17 at 12:36
  • If you're using tools like gatsby, it could be server side or browser side. – Pete - MSFT Mar 25 '20 at 05:29
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"]();
Chetan S
  • 23,637
  • 2
  • 63
  • 78
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).

Misaz
  • 3,694
  • 2
  • 26
  • 42
  • This is identical to the deleted answer from 3 years before. – RobG Jun 01 '17 at 23:22
  • 3
    I don't know about it and don't know why it was deleted? – Misaz Jun 02 '17 at 15:13
  • It was deleted because `eval` opens up a security risk for client-side users. Unless you're using javascript in closed production, the best advice is to not use `eval`. It's good practice to not use it in the first place because it may become a bad habit. – Jacksonkr Jul 05 '17 at 18:44
  • 2
    @Jacksonkr, does that also mean I shouldn't drink a beer in the first place because it may become a bad habit? :) – Vince Horst Jul 14 '17 at 19:50
  • 3
    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. – Jacksonkr Jul 14 '17 at 21:25
  • 9
    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. – MacroMan Jul 11 '18 at 10:09
  • 1
    Exactly MacroMan. For it to be deleted as an answer is ridiculous. – Lee Sep 18 '18 at 08:08
  • Despite this solution is dangerous, it actually works in every environment, specifically in node.js. – loretoparisi Feb 13 '19 at 15:59
  • 1
    @loretoparisi unfortunately its the only one working for me until I find a safer working solution :( – PowerAktar Jan 07 '20 at 17:05
  • There is nothing wrong with using eval. It isn't anything like driving drunk. Just don't let users pass their own input to eval. Do that and you'll be fine. If eval is bad then so is setTimeout which is frequently used and new Function("...") which is in another working answer posted here. – PHP Guru Nov 15 '20 at 20:32
7

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)"
Niki Romagnoli
  • 1,406
  • 1
  • 21
  • 26
  • 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 Jul 06 '23 at 01:23
5
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");
Xakiru
  • 2,556
  • 1
  • 15
  • 11
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;
};
pjesi
  • 3,931
  • 3
  • 20
  • 16
2

This works:

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

let profiles = {
  Class1,
  Class2
}

let profileAsString = 'Class1'
new profiles[profileAsString]()
Mescalina
  • 61
  • 3
1

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!

Abraham Brookes
  • 1,720
  • 1
  • 17
  • 32
0

myOption = {.......}

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

-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.
General Grievance
  • 4,555
  • 31
  • 31
  • 45
Antony
  • 1
  • 1
  • 1