javascript 在JavasScript ECMAScript 6中根据类名创建对象

km0tfn4u  于 2023-02-21  发布在  Java
关注(0)|答案(8)|浏览(138)

我想使用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');

我做错了什么?

vc6uscn9

vc6uscn91#

不要把类名放在那个对象上,把类本身放在那里,这样你就不必依赖于它们是全局的,并且可以通过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);
    }
};
qnzebej0

qnzebej02#

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

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

现在您可以创建如下的类:

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

aiazj4mn3#

问题是类不是窗口对象的属性,你可以让一个对象的属性“指向”你的类:

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

vc9ivgsu4#

对于那些没有使用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

93ze6v8z

93ze6v8z5#

我更喜欢这种方法:
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'];
tag5nh1u

tag5nh1u6#

我知道这是一篇老文章,但最近我也遇到了同样的问题,即如何动态地示例化一个类
我使用的是webpack,因此按照文档说明,可以使用***import()***函数动态加载模块

js/类/我的类.js

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

js/应用程序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);
});

注意: 这个方法是异步的,我不能确定它的性能代价,但是它在我的简单程序上工作得很好(值得一试^^)
***附言 *:**说实话,我是Es6的新手(几天),我更像是一个C++ / PHP / Java开发人员。

我希望这对任何遇到这个问题的人都有帮助,这不是一个坏的做法。

6g8kf2rb

6g8kf2rb7#

    • 澄清**

还有类似的问题,包括这个被关闭的SO question,它们在JavaScript中寻找代理类或工厂函数;也称为动态类。这个答案是一个现代的解决方案,以防你降落在这个答案寻找任何这些东西。

    • 答案/解决方案**

到2022年,我认为有一个更优雅的解决方案可以在浏览器中使用,我创建了一个名为Classes的类,它在窗口上自注册属性Class(大写C);下面的代码示例。
现在,您可以让希望能够动态引用的类全局注册它们自己:

// 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);
    • 代码**

你可以看到latest code on my gist,在所有其他脚本之前添加这个脚本,你的所有类都可以注册到它。

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

qacovj5a8#

这是一个古老的问题,但我们可以找到三个非常聪明和有用的主要方法:
1.丑陋的人
我们可以使用eval来示例化我们的类,如下所示:

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

然而,eval有一个很大的警告,如果我们不清理,不添加一些安全层,那么我们将有一个很大的安全整体,可以暴露。

2.善

如果我们知道将要使用的类名,那么我们可以创建一个如下所示的查找表:

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.模式

最后,我们可以创建一个Factory类,它将安全地处理允许的类,如果可以加载它,则抛出一个错误。

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 create(name, ...p) {
    return new(ClassFactory.class(name))(...p)
  }
}

// make a class from a string
let object
object = ClassFactory.create('Column', 'box')
object = ClassFactory.create('Sequence', 'box', 'index')
object = ClassFactory.create('Checkbox', 'box')

我推荐The Good方法。它是干净和安全的。而且,它应该比使用globalwindow对象更好:

  • ES6中的class定义不会像其他顶级变量声明那样自动放在global对象上(JavaScript试图避免在以前的设计错误上添加更多垃圾)。
  • 因此,我们不会污染global对象,因为我们使用本地classMap对象来查找所需的class

相关问题