如何使用变量字符串定义类名来创建JavaScript对象?

88

我想做的是这样的——这只是伪代码,无法正常工作。有没有人知道如何真正实现:

// 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个回答

74

如果你这样做,会起作用吗:

var myObject = window[classNameString];
很抱歉,我只能够用英文进行回答。

1
你是说window [classNameString](不带引号)。只要将MyClass移动到较低的范围(即function),它就会中断。@kirk:是的,这是跨浏览器的。 - Crescent Fresh
1
你如何使用这种方法将参数传递给构造函数? - James McMahon
3
在这种情况下,你需要使用window[classNameString](args)。但正如Crescent Fresh所提到的那样,要小心,因为在某些情况下可能会出现错误。 - peirix
12
这好像对我不起作用。经过一些试错,我发现这个可以:var obj = new Home(id);,但是这个不行:var obj = new window["Home"](id);。我正试图让这个工作:new window[x](id); 其中 x = "Home"... 我得到的错误是 Uncaught TypeError: window[x] is not a constructor - Abraham Murciano Benzadon
3
警告:这个不适用于ES6类 - Niki Romagnoli
显示剩余8条评论

58

这里有一个更健壮的解决方案,可以与命名空间函数一起使用:

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;
};

示例:

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();

为什么要写 (windows || this),window 不是一直都会被定义吗? - James McMahon
12
@JamesMcMahon:我们所知道的世界已不再是现实。像Node.js这样的技术也已经占据了我们的星球! :) - Mrchief
自从严格模式出现(ECMA-262 ed 5于2009年),如果在非浏览器环境中没有通过调用设置thiswindow || this可能会返回undefined(在示例中没有设置)。 - RobG
嗨,如何使用它并使其适用于已导入的类?例如,从'../../mazes/components/Grid'导入Grid。 - preston

38

顺便提一句:在浏览器JavaScript中,window是指全局对象。它也是this,即使在非浏览器环境(如Node.js,Chrome扩展程序,转译代码等)也应该能正常使用。

var obj = new this[classNameString]();

限制在于被调用的类必须在全局上下文中。如果要将同样的操作应用于作用域类,则需要执行以下操作:

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

然而,实际上没有理由使用字符串。JavaScript函数本身就是对象,就像字符串一样也是对象。

编辑

这里有一个更好的方法可以获取全局范围,在严格模式和非浏览器JS环境中均可正常工作:

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

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

来源:如何在JavaScript中获取全局对象?


12
只有在全局上下文中调用时,它才是 this。而 window 则不管你处于哪个上下文中都可以使用,因此我认为没有理由优先选择 this 而不是 window - devios1
6
就像答案所说的那样,你所处的环境不一定是浏览器。因此,在非浏览器环境中,window可能未定义或与你预期的不同,使用this更为可取。 - XedinUnknown
2
@Sebi,在问题中没有暗示环境是客户端。另外,客户端也不一定意味着“浏览器”。 回答提供了通过显式引用全局上下文来满足OP需求的方法 - 这就是重点。 我指出唯一可靠的从全局上下文引用全局上下文的方法是this,而不是window。 此外,从全局上下文引用最可靠的方式可能是 top。 同时,将其注入到闭包中也可能是一个好的选择。 - XedinUnknown
1
如果你测试这段代码,你会发现它可以在任何上下文中工作,而不仅仅是全局上下文。这就是为什么我们使用 Function('return this')() 而不是 return this。前者会自动切换到全局上下文。这种方法可以在任何上下文中工作,而且 window 可以被覆盖,只适用于浏览器。不知道代码执行上下文来编写跨平台代码是很好的。我给出的答案是跨平台的,不能被覆盖,因此比使用 window 更好。它可以在转译代码中工作,例如 webpack、gulp 和 node、扩展等。请先进行测试。 - bucabay
@devios1 我认为这里的混淆在于函数必须在全局作用域中。调用上下文可以是任何作用域。这和使用 window 是相同的,但没有 window 的问题。调用上下文可以是任何作用域。 - bucabay
显示剩余2条评论

14

如果 MyClass 是全局的,你可以使用下标符号作为 window 对象的一个属性来访问它(假设你的代码在浏览器中运行)。

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

这个在所有浏览器中都能工作吗?我看到一些帖子抱怨它对他们不起作用。 - PHP Guru

11
如果classNameString是来自安全来源,您可以使用。
var classNameString = 'MyClass';
var myObject = eval("new " + classNameString + "()");

这个解决方案使用命名空间,与平台(浏览器/服务器)无关。


3
我不知道此事,也不知道为什么它被删除了? - Misaz
2
@Jacksonkr,这是否意味着我一开始就不应该喝啤酒,因为它可能会成为一个坏习惯? :) - Vince Horst
3
根据你的比喻:当你听说使用 eval 不好时,却仍然使用它就像是酒后驾车。"上次我喝酒开车没事发生...嗝...事实上我喝醉了开车反而开得更好! ...嗝..." 等到出了问题再想避免已经太迟了。不要成为那样的人。 - Jacksonkr
9
主张永远不使用函数eval或其他形式的函数是错误的建议。如果一个函数没有任何用例,它早就会被废弃并作为特性移除了。 - MacroMan
1
确切的说,MacroMan。将其作为答案删除是荒谬的。 - Lee
显示剩余5条评论

8

浏览器全局对象是window,每当您使用var定义全局变量或使用function定义函数时,都会将它们添加到window中。 因此,您可以在那里获取您的“类”定义:

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

但是对于ES6类声明,这种方法不起作用。
使用ES6关键字“class”声明的类被处理方式与标准不同。
使用“class MyClass {}”声明的类定义了一个全局类,但它不会成为window全局对象的属性。换句话说,以下内容适用:
class MyClass {};
typeof window.MyClass === 'undefined';

那么,如何使用ES6类完成相同的操作呢?需要使用对象访问符号来解析字符串名称,但要搜索的父对象不再可用。一种方法是创建自己的上下文对象,在其中声明您的类并在其中搜索。代码如下:
// 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)"

我真的很喜欢这种方法,因为在我的应用程序中,这正是我所需要的。不过,我有一个问题,有点相关,也有点离题。当你在单独的文件中定义你的类时,你会如何实现这一点呢?以前,我可以分别导入所有这些类,但现在我不太确定如何“运行”我的类文件。这样它就会将这个类追加到可用类的列表中(希望这样说得清楚)。因为我的 main.js 看不到这些类(显然,因为对于我的 main.js 来说,这些类从未被定义过)。顺便说一下,我正在制作一个在 Chrome 中运行的应用程序。 - Jelmer

5
function myClass(arg){
}

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

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

编辑:内联示例

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

3
这里是Yuriy方法的改进版本,同时还处理对象。
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;
};

2

这个有效:

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

let profiles = {
  Class1,
  Class2
}

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

1

在我的情况下,我正在从服务器预加载数据,并尝试使用类加载预加载的数据(特别是,我正在使用vuex-orm)。我选择了一个模型列表,并将它们映射到我已经在文件顶部导入的类,而不是采用任何超级花哨的东西。请看以下示例:

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})
    }
})

明确、安全、简单。不错!


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接