在 Javascript ECMAScript 6 中从 class 名称创建对象

Create object from class name in JavasScript ECMAScript 6

我想使用 ES6 创建对象工厂,但旧式语法不适用于新语法。

我有下一个代码:

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

我做错了什么?

问题是 类 不是 window 对象的属性。您可以将具有属性 "pointing" 的对象改为 类:

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

不要在该对象上放置 class 名称。将 classes 本身放在那里,这样您就不必依赖它们是全局的并且可以通过 window.

访问(在浏览器中)

顺便说一句,没有充分的理由让这个工厂成为 class,您可能只会实例化它一次(单例)。把它变成一个对象:

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

对于那些不使用 ES6 并且想知道如何使用字符串创建 classes 的人,这里是我为使它起作用所做的工作。

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

如您所见,最简单的方法是将 class 添加到对象。

这是 fiddle: https://jsfiddle.net/zxg7dsng/1/

下面是一个使用这种方法的示例项目: https://github.com/pdxjohnny/dist-rts-client-web

有一个小而肮脏的方法可以做到这一点:

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

您现在可以像这样创建 class:

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

我更喜欢这种方法:

allThemClasses.js

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

script.js

import * as Classes from './allThemClasses';

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

我知道这是一个旧的 post,但最近我遇到了关于如何动态实例化 class 的相同问题

我正在使用 webpack so following the documentation 有一种方法可以使用 import() 函数动态加载模块

js/classes/MyClass.js

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

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

注意:这个方法是异步的,我无法真正说出它的性能成本, 但它在我的简单程序上完美运行(值得一试^^)

Ps: 顺便说一下,我是 Es6 的新手(几天)我更C++ / PHP / Java 开发人员。

我希望这能帮助遇到这个问题的任何人,这不是一个坏习惯 ^^"。

澄清
还有类似的问题,包括这个已经关闭的,正在寻找JavaScript中的代理classes或者工厂函数;也称为动态 classes。这个答案是一个现代解决方案,以防您在寻找任何这些东西时找到这个答案。

答案/解决方案
截至 2022 年,我认为在浏览器中使用更优雅的解决方案。我做了一个class叫Classes,self-registers 属性 Class(大写C)在window上;示例下方的代码。

现在您可以让 class 那些您希望能够在全局范围内动态注册自己的引用:

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

稍后在您的代码中,您只需要 class 的名称即可获得其原始参考:

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

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

或者,甚至更简单,直接实例化它:

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

代码
你可以看到latest code on my gist。在所有其他脚本之前添加此脚本,您的所有 classes 都将能够使用它注册。

/**
 * 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;