简体   繁体   English

从 JavasScript ECMAScript 6 中的 class 名称创建 object

[英]Create object from class name in JavasScript ECMAScript 6

I want create object factory using ES6 but old-style syntax doesn't work with new.我想使用 ES6 创建 object 工厂,但旧式语法不适用于新式。

I have next code:我有下一个代码:

export class Column {}
export class Sequence {}
export class Checkbox {}

export class ColumnFactory {
    constructor() {
        this.specColumn = {
            __default: 'Column',
            __sequence: 'Sequence',
            __checkbox: 'Checkbox'
        };
    }

    create(name) {
        let className = this.specColumn[name] ? this.specColumn[name] : this.specColumn['__default'];
        return new window[className](name); // this line throw error
    }
}

let factory = new ColumnFactory();
let column = factory.create('userName');

What do I do wrong?我做错了什么?

Don't put class names on that object.不要把类名放在那个对象上。 Put the classes themselves there, so that you don't have to rely on them being global and accessible (in browsers) through window .将类本身放在那里,这样您就不必依赖它们是全局的并且可以通过window访问(在浏览器中)。

Btw, there's no good reason to make this factory a class, you would probably only instantiate it once (singleton).顺便说一句,没有充分的理由让这个工厂成为一个类,你可能只会实例化一次(单例)。 Just make it an object:把它变成一个对象:

export class Column {}
export class Sequence {}
export class Checkbox {}

export const columnFactory = {
    specColumn: {
        __default: Column,    // <--
        __sequence: Sequence, // <--
        __checkbox: Checkbox  // <--
    },
    create(name, ...args) {
        let cls = this.specColumn[name] || this.specColumn.__default;
        return new cls(...args);
    }
};

There is a small & dirty way to do that:有一种小而肮脏的方法可以做到这一点:

function createClassByName(name,...a) {
    var c = eval(name);
    return new c(...a);
}

You can now create a class like that:您现在可以创建一个这样的类:

let c = createClassByName( 'Person', x, y );

The problem is that the classes are not properties of the window object.问题是这些类不是 window 对象的属性。 You can have an object with properties "pointing" to your classes instead:您可以使用一个属性“指向”您的类的对象:

class Column {}
class Sequence {}
class Checkbox {}
let classes = {
  Column,
  Sequence,
  Checkbox 
}

class ColumnFactory {
    constructor() {
        this.specColumn = {
            __default: 'Column',
            __sequence: 'Sequence',
            __checkbox: 'Checkbox'
        };
    }

    create(name) {
        let className = this.specColumn[name] ? this.specColumn[name] : this.specColumn['__default'];
        return new classes[className](name); // this line no longer throw error
    }
}

let factory = new ColumnFactory();
let column = factory.create('userName');

export {ColumnFactory, Column, Sequence, Checkbox};

Clarification澄清
There are similar questions to this, including this SO question that was closed, that are looking for proxy classes or factory functions in JavaScript;有与此类似的问题,包括已关闭的SO question ,它们正在 JavaScript 中寻找代理类或工厂函数; also called dynamic classes.也称为动态类。 This answer is a modern solution in case you landed on this answer looking for any of those things.这个答案是一个现代的解决方案,以防你在这个答案上寻找任何这些东西。

Answer / Solution答案/解决方案
As of 2022 I think there is a more elegant solution for use in the browser.截至 2022 年,我认为在浏览器中使用更优雅的解决方案。 I made a class called Classes that self-registers the property Class (uppercase C) on the window;我制作了一个名为Classes的 class,它在 window 上自行注册属性Class (大写 C); code below examples.下面的代码示例。

Now you can have classes that you want to be able to reference dynamically register themselves globally:现在你可以让你希望能够动态引用的类在全局范围内注册自己:

// Make a class:
class Handler {
    handleIt() {
        // Handling it...
    }
}

// Have it register itself globally:
Class.add(Handler);

// OR if you want to be a little more clear:
window.Class.add(Handler);

Then later on in your code all you need is the name of the class you would like to get its original reference:然后稍后在您的代码中,您只需要 class 的名称即可获得其原始参考:

// Get class
const handler = Class.get('Handler');

// Instantiate class for use
const muscleMan = new (handler)();

Or, even easier, just instantiate it right away:或者,更简单的是,立即实例化它:

// Directly instantiate class for use
const muscleMan = Class.new('Handler', ...args);

Code代码
You can see the latest code on my gist .你可以在我的 gist 上看到最新的代码 Add this script before all other scripts and all of your classes will be able to register with it.在所有其他脚本之前添加此脚本,您的所有课程都将能够注册它。

/**
 * Adds a global constant class that ES6 classes can register themselves with.
 * This is useful for referencing dynamically named classes and instances
 * where you may need to instantiate different extended classes.
 *
 * NOTE: This script should be called as soon as possible, preferably before all
 * other scripts on a page.
 *
 * @class Classes
 */
class Classes {

    #classes = {};

    constructor() {
        /**
         * JavaScript Class' natively return themselves, we can take advantage
         * of this to prevent duplicate setup calls from overwriting the global
         * reference to this class.
         *
         * We need to do this since we are explicitly trying to keep a global
         * reference on window. If we did not do this a developer could accidentally
         * assign to window.Class again overwriting any classes previously registered.
         */
        if (window.Class) {
            // eslint-disable-next-line no-constructor-return
            return window.Class;
        }
        // eslint-disable-next-line no-constructor-return
        return this;
    }

    /**
     * Add a class to the global constant.
     *
     * @method
     * @param {Class} ref The class to add.
     * @return {boolean} True if ths class was successfully registered.
     */
    add(ref) {
        if (typeof ref !== 'function') {
            return false;
        }
        this.#classes[ref.prototype.constructor.name] = ref;
        return true;
    }

    /**
     * Checks if a class exists by name.
     *
     * @method
     * @param {string} name The name of the class you would like to check.
     * @return {boolean} True if this class exists, false otherwise.
     */
    exists(name) {
        if (this.#classes[name]) {
            return true;
        }
        return false;
    }

    /**
     * Retrieve a class by name.
     *
     * @method
     * @param {string} name The name of the class you would like to retrieve.
     * @return {Class|undefined} The class asked for or undefined if it was not found.
     */
    get(name) {
        return this.#classes[name];
    }

    /**
     * Instantiate a new instance of a class by reference or name.
     *
     * @method
     * @param {Class|name} name A reference to the class or the classes name.
     * @param  {...any} args Any arguments to pass to the classes constructor.
     * @returns A new instance of the class otherwise an error is thrown.
     * @throws {ReferenceError} If the class is not defined.
     */
    new(name, ...args) {
        // In case the dev passed the actual class reference.
        if (typeof name === 'function') {
            // eslint-disable-next-line new-cap
            return new (name)(...args);
        }
        if (this.exists(name)) {
            return new (this.#classes[name])(...args);
        }
        throw new ReferenceError(`${name} is not defined`);
    }

    /**
     * An alias for the add method.
     *
     * @method
     * @alias Classes.add
     */
    register(ref) {
        return this.add(ref);
    }

}

/**
 * Insure that Classes is available in the global scope as Class so other classes
 * that wish to take advantage of Classes can rely on it being present.
 *
 * NOTE: This does not violate https://www.w3schools.com/js/js_reserved.asp
 */
const Class = new Classes();
window.Class = Class;

For those of you that are not using ES6 and want to know how you can create classes by using a string here is what I have done to get this to work.对于那些不使用 ES6 并想知道如何通过使用字符串创建类的人,这是我为使其工作所做的工作。

"use strict";

class Person {
    constructor(x, y) {
        this.x = x;
        this.y = y;
    }
}
window.classes = {};
window.classes.Person = Person;

document.body.innerText = JSON.stringify(new window.classes["Person"](1, 2));

As you can see the easiest way to do this is to add the class to an object.如您所见,最简单的方法是将类添加到对象中。

Here is the fiddle: https://jsfiddle.net/zxg7dsng/1/这是小提琴: https : //jsfiddle.net/zxg7dsng/1/

Here is an example project that uses this approach: https://github.com/pdxjohnny/dist-rts-client-web这是使用这种方法的示例项目: https : //github.com/pdxjohnny/dist-rts-client-web

I prefer this method:我更喜欢这种方法:

allThemClasses.js allThemClasses.js

export class A {}
export class B {}
export class C {}

script.js脚本.js

import * as Classes from './allThemClasses';

const a = new Classes['A'];
const b = new Classes['B'];
const c = new Classes['C'];

I know this is an old post, but recently I've had the same question about how to instance a class dynamically我知道这是一篇旧帖子,但最近我对如何动态实例化类有同样的问题

I'm using webpack so following the documentation there is a way to load a module dynamically using the import() function我正在使用webpack,所以按照文档,有一种方法可以使用import()函数动态加载模块

js/classes/MyClass.js js/classes/MyClass.js

class MyClass {
    test = null;
    constructor(param) {
        console.log(param)
        this.test = param;
    }
}

js/app.js js/app.js

var p = "example";
var className = "MyClass";

import('./classes/'+className).then(function(mod) {
    let myClass = new mod[className](p);
    console.log(myClass);
}, function(failMsg) {
    console.error("Fail to load class"+className);
    console.error(failMsg);
});

Beware: this method is asynchronous and I can't really tell the performance cost for it , But it works perfectly on my simple program (worth a try ^^)请注意:此方法是异步的,我无法真正说出它的性能成本,但它在我的简单程序上运行良好(值得一试^^)

Ps : To be fare I'm new to Es6 (a couple of days) I'm more a C++ / PHP / Java developer. Ps顺便说一句,我是 Es6 的新手(几天),我更像是一名 C++/PHP/Java 开发人员。

I hope this helps anyone that come across this question and that is it not a bad practice ^^".我希望这可以帮助遇到这个问题的任何人,这不是一个坏习惯 ^^”。

This is an old question but I think there are 3 main approaches that are very clever and useful:这是一个老问题,但我认为有 3 种主要方法非常聪明和有用:

1. The Ugly 1.丑陋的

We can use eval to instantiate our class like this:我们可以使用eval来实例化我们的 class,如下所示:

 class Column { constructor(c) { this.c = c console.log(`Column with ${this.c}`); } } function instantiator(name,...params) { const c = eval(name) return new c(...params) } const name = 'Column'; const column = instantiator(name, 'box') console.log({column})

However, eval has a big caveat, if we don't sanitize and we don't add some layers of security then we will have a big security whole that can be expose.但是, eval有一个很大的警告,如果我们不清理并且不添加一些安全层,那么我们将拥有一个可以暴露的大安全整体。

2. The Good 2. 好

If we know the classes that we will use then we can create a lookup table like this:如果我们知道我们将使用的类,那么我们可以创建一个如下所示的查找表:

 class Column { constructor(c) { console.log(`Column with ${c}`) } } class Sequence { constructor(a, b) { console.log(`Sequence with ${a} and ${b}`) } } class Checkbox { constructor(c) { console.log(`Checkbox with ${c}`) } } // construct dict object that contains our mapping between strings and classes const classMap = new Map([ ['Column', Column], ['Sequence', Sequence], ['Checkbox', Checkbox], ]) function instantiator(name,...p) { return new(classMap.get(name))(...p) } // make a class from a string let object = instantiator('Column', 'box') object = instantiator('Sequence', 'box', 'index') object = instantiator('Checkbox', 'box')

3. The Pattern 3. 模式

Finally, we can just create a Factory class that will safety handle the allowed classes and throw an error if it can load it.最后,我们可以创建一个工厂 class 来安全地处理允许的类,如果可以加载它就会抛出错误。

 class Column { constructor(c) { console.log(`Column with ${c}`) } } class Sequence { constructor(a, b) { console.log(`Sequence with ${a} and ${b}`) } } class Checkbox { constructor(c) { console.log(`Checkbox with ${c}`) } } class ClassFactory { static class(name) { switch (name) { case 'Column': return Column case 'Sequence': return Sequence case 'Checkbox': return Checkbox default: throw new Error(`Could not instantiate ${name}`); } } static instantiator(name,...p) { return new(ClassFactory.class(name))(...p) } } // make a class from a string let object object = ClassFactory.instantiator('Column', 'box') object = ClassFactory.instantiator('Sequence', 'box', 'index') object = ClassFactory.instantiator('Checkbox', 'box')

I recommend The Good method.我推荐好的方法。 It is is clean and safe.它既干净又安全。 Also, it should be better than using global or window object:此外,它应该比使用globalwindow object 更好:

  • class definitions in ES6 are not automatically put on the global object like they would with other top level variable declarations (JavaScript trying to avoid adding more junk on top of prior design mistakes). ES6 中的class定义不会像其他顶级变量声明那样自动放在global object 上(JavaScript 试图避免在先前的设计错误之上添加更多垃圾)。

  • Therefore we will not pollute the global object because we are using a local classMap object used for lookup the required class .因此,我们不会污染global object 因为我们使用的是本地classMap object 用于查找所需的class

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM