Создать объект из строки в 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');

Что я делаю не так?

9 ответов

Решение

Не помещайте имена классов в этот объект. Поместите сами классы туда, чтобы вам не приходилось полагаться на их глобальность и доступность (в браузерах) через window,

Кстати, нет веской причины делать эту фабрику классом, вы, вероятно, создадите ее только один раз (синглтон). Просто сделайте это объектом:

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

Есть маленький и грязный способ сделать это:

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

Теперь вы можете создать такой класс:

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

Проблема в том, что классы не являются свойствами объекта окна. Вместо этого вы можете иметь объект со свойствами, указывающими на ваши классы:

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

Я предпочитаю этот метод:

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

Уточнение.
Есть похожие вопросы, в том числе этот вопрос SO , который был закрыт, который ищет прокси-классы или фабричные функции в JavaScript; также называемые динамическими классами. Этот ответ является современным решением на случай, если вы попали на этот ответ в поисках любой из этих вещей.

Ответ/решение
По состоянию на 2022 год я думаю, что есть более элегантное решение для использования в браузере. Я сделал класс под названием Classesкоторый самостоятельно регистрирует собственность 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);

Затем позже в вашем коде все, что вам нужно, это имя класса, для которого вы хотите получить исходную ссылку:

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

Код
Вы можете увидеть последний код в моей сути . Добавьте этот скрипт перед всеми остальными скриптами, и все ваши классы смогут зарегистрироваться в нем.

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

Для тех из вас, кто не использует 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));

Как вы видите, самый простой способ сделать это - добавить класс к объекту.

Вот скрипка: https://jsfiddle.net/zxg7dsng/1/

Вот пример проекта, который использует этот подход: https://github.com/pdxjohnny/dist-rts-client-web

Я знаю, что это старый пост, но недавно у меня возник тот же вопрос о том, как динамически создавать экземпляры класса.

Я использую Webpack так после документации есть способ загрузить модуль динамически с помощью импорта () функции

js/ классы / 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.

Надеюсь, это поможет любому, кто столкнется с этим вопросом, и это неплохая практика ^^".

Это старый вопрос, но мы можем найти три основных подхода, которые очень умны и полезны:

1. Уродливый

Мы можем использоватьevalчтобы создать экземпляр нашего класса следующим образом:

Я рекомендую хороший метод. Это чисто и безопасно. Кроме того, это должно быть лучше, чем использование илиwindowобъект:

  • определения в ES6 не помещаются в объект автоматически, как это было бы с другими объявлениями переменных верхнего уровня (JavaScript пытается избежать добавления большего количества мусора поверх предыдущих ошибок проектирования).

  • Поэтому мы не будем загрязнятьglobalобъект, потому что мы используем локальныйclassMapобъект для поиска требуемогоclass.

Мне показалось, что это легко реализовать на TypeScript. Давайте создадим существующий класс Test с существующим testMethod. Вы можете динамически инициировать свой класс с помощью строковых переменных.

      class Test {
     constructor()
     {
     }
     testMethod() 
     { 
     }
   }

    // Class name and method strings 
    let myClassName = "Test";
    let myMethodName = "testMethod";

    let myDynamicClass = eval(myClassName);

    // Initiate your class dynamically
    let myClass = new myDynamicClass();

    // Call your method dynamically
    myClass[myMethod]();
Другие вопросы по тегам