48

我希望能够实例化一个打字稿类,在其中我在运行时获取类和构造函数的详细信息。我想编写的函数将接受类名和构造函数参数。

export function createInstance(moduleName : string, className : string, instanceParameters : string[]) {
    //return new [moduleName].[className]([instancePameters]); (THIS IS THE BIT I DON'T KNOW HOW TO DO)
}
4

9 回答 9

30

你可以试试:

var newInstance = Object.create(window[className].prototype);
newInstance.constructor.apply(newInstance, instanceparameters);
return newInstance;

编辑此版本正在使用 TypeScript 操场,例如:

class Greeter {
    greeting: string;
    constructor(message: string) {
        this.greeting = message;
    }
    greet() {
        return "Hello, " + this.greeting;
    }
}

//instance creation here
var greeter = Object.create(window["Greeter"].prototype);
greeter.constructor.apply(greeter, new Array("World"));

var button = document.createElement('button');
button.innerText = "Say Hello";
button.onclick = function() {
    alert(greeter.greet());
}

document.body.appendChild(button);
于 2013-03-11T13:09:07.097 回答
25

当您使用 TypeScript 时,我假设您希望输入加载的对象。所以这里是示例类(和一个接口,因为您选择加载许多实现之一,例如)。

interface IExample {
    test() : string;
}

class Example {
    constructor (private a: string, private b: string) {

    }

    test() {
        return this.a + ' ' + this.b;
    }
}

所以你会使用某种加载器来给你一个实现:

class InstanceLoader {
    constructor(private context: Object) {

    }

    getInstance(name: string, ...args: any[]) {
        var instance = Object.create(this.context[name].prototype);
        instance.constructor.apply(instance, args);
        return instance;
    }
}

然后像这样加载它:

var loader = new InstanceLoader(window);

var example = <IExample> loader.getInstance('Example', 'A', 'B');
alert(example.test());

目前,我们有一个演员表:<IExample>- 但是当添加泛型时,我们可以取消它并使用泛型代替。它看起来像这样(记住它还不是语言的一部分!)

class InstanceLoader<T> {
    constructor(private context: Object) {

    }

    getInstance(name: string, ...args: any[]) : T {
        var instance = Object.create(this.context[name].prototype);
        instance.constructor.apply(instance, args);
        return <T> instance;
    }
}

var loader = new InstanceLoader<IExample>(window);

var example = loader.getInstance('Example', 'A', 'B');
于 2013-03-11T14:38:22.960 回答
23

更新

为了让它在最新的 TypeScript 中工作,你现在需要将命名空间转换为any. 否则你会得到一个Error TS7017 Build:Element implicitly has an 'any' type because type '{}' has no index signature.

如果你有一个特定的命名空间/模块,对于你想要创建的所有类,你可以简单地这样做:

var newClass: any = new (<any>MyNamespace)[classNameString](parametersIfAny);

更新:没有命名空间使用new (<any>window)[classname]()

在 TypeScript 中,如果您在命名空间之外声明一个类,它会为“类函数”生成一个 var。这意味着它是针对当前范围存储的(很可能window除非您在另一个范围内运行它,例如 nodejs)。这意味着你可以这样做new (<any>window)[classNameString]

这是一个工作示例(所有代码,没有命名空间):

class TestClass
{
    public DoIt()
    {
        alert("Hello");
    }
}

var test = new (<any>window)["TestClass"]();
test.DoIt();

要了解它的工作原理,生成的 JS 代码如下所示:

var TestClass = (function () {
    function TestClass() {
    }
    TestClass.prototype.DoIt = function () {
        alert("Hello");
    };
    return TestClass;
}());
var test = new window["TestClass"]();
test.DoIt();
于 2016-06-30T15:36:39.033 回答
20

这适用于带有 ES6 模块的 TypeScript 1.8:

import * as handlers from './handler';

function createInstance(className: string, ...args: any[]) {
  return new (<any>handlers)[className](...args);
}

类在handler模块中导出。它们可以从其他模块重新导出。

export myClass {};
export classA from './a';
export classB from './b';

至于在参数中传递模块名称,我无法使其工作,因为 ES6 模块无法动态加载。

于 2016-03-04T03:50:15.580 回答
7

另一种方法是动态调用文件并new

// -->Import: it dynamically
const plug = await import(absPath);
const constructorName = Object.keys(plug)[0];

// -->Set: it
const plugin = new plug[constructorName]('new', 'data', 'to', 'pass');
于 2018-01-28T14:59:22.220 回答
6

从 typescript 0.9.1 开始,您可以执行类似以下操场的操作:

class Handler {
    msgs:string[];  
    constructor(msgs:string[]) {
        this.msgs = msgs;
    }
    greet() {
        this.msgs.forEach(x=>alert(x));
    }
}

function createHandler(handler: typeof Handler, params: string[]) {
    var obj = new handler(params);
    return obj;
}

var h = createHandler(Handler, ['hi', 'bye']);
h.greet();
于 2013-09-21T08:32:22.693 回答
1

我找到了另一种方式,因为在我的情况下,我无法访问 window.

要创建的示例类:

class MyService {

  private someText: string;

  constructor(someText: string) {
    this.someText = someText;
  }

  public writeSomeText() {
    console.log(this.someText);
  }
}

工厂类:

interface Service<T> {
  new (param: string): T;
}

export class ServiceFactory<T> {

  public createService(ctor: Service<T>, param: string) {
    return new ctor(param);
  }

}

然后使用工厂创建实例:

const factory: ServiceFactory<MyService> = new ServiceFactory<MyService>();
const service: MyService = factory.createService(MyService, 'Hello World');
service.writeSomeText();
于 2018-06-22T17:33:24.677 回答
0
function fromCamelCase(str: string) {
  return str
    // insert a '-' between lower & upper
    .replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
}

async getViewModelFromName(name: string) {
    //
    // removes the 'ViewModel' part ('MyModelNameViewModel' = 'MyModelName').
    let index = name.indexOf('ViewModel');
    let shortName = index > 0 ? name.substring(0, index) : name;

    // gets the '-' separator representation of the camel cased model name ('MyModelName' = 'my-model-name').
    let modelFilename = fromCamelCase(shortName) + '.view-model';

    var ns = await import('./../view-models/' + modelFilename);

    return new ns[name]();
  }

或者

declare var require: any; // if using typescript.

getInstanceByName(name: string) {
    let instance;

    var f = function (r) {
      r.keys().some(key => {
        let o = r(key);
        return Object.keys(o).some(prop => {
          if (prop === name) {
            instance = new o[prop];
            return true;
          }
        })
      });
    }
    f(require.context('./../view-models/', false, /\.view-model.ts$/));

    return instance;
}
于 2018-04-25T19:47:32.187 回答
-2

在某些特殊情况下,使用 是合理的eval

namespace org {
  export namespace peval {
    export class MyClass {
      constructor() {

      }

      getText(): string {
        return 'any text';
      }
    }
  }
}

const instance = eval('new org.peval.MyClass();');

console.log(instance.getText());

注意:如果不是必需的,则不应使用eval,因为它将以调用者的权限执行字符串中包含的代码。请参阅:eval() - JavaScript | MDN

在安全的情况下,当您知道代码字符串的来源和作用时(尤其是当您知道它不是来自用户输入时),您可以使用它。在上面描述的案例中,我们使用我们对 TypeScript 类名及其包的了解来创建一个新实例。

于 2019-04-10T17:36:45.910 回答