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