我有一个不寻常的情况,我需要动态生成一个类原型,然后使用反射工具(无法更改)遍历原型并找到所有方法。如果我可以对方法进行硬编码,类定义将如下所示:
class Calculator {
constructor(name) { this.name = name; }
}
class AdditionCalculator extends Calculator {
constructor(name) { super(name); }
add(left, right) { console.log(this.name + (left + right)); }
}
class SubtractCalculator extends AdditionCalculator {
constructor(name) { super(name); }
subtract(left, right) { console.log(this.name + (left - right)); }
}
但是,我无法对方法/继承进行硬编码,因为我需要大量数据驱动的方法组合:例如,我需要一个没有数学方法的计算器、一个仅包含 Add() 的计算器、一个包含仅 Subtract()、带有 Add() 和 Subtract() 的计算器,以及更多组合。
有没有办法用 JavaScript 来做到这一点(我假设是用原型)?
作为参考,这里是查找方法的反射工具:
function getMethods(obj:object, deep:number = Infinity):Array<string> {
let props:string[] = new Array<string>()
type keyType = keyof typeof obj
while (
(obj = Object.getPrototypeOf(obj)) && // walk-up the prototype chain
Object.getPrototypeOf(obj) && // not the the Object prototype methods (hasOwnProperty, etc...)
deep !== 0
) {
const propsAtCurrentDepth:string[] = Object.getOwnPropertyNames(obj)
.concat(Object.getOwnPropertySymbols(obj).map(s => s.toString()))
.sort()
.filter(
(p:string, i:number, arr:string[]) =>
typeof obj[<keyType>p] === 'function' && // only the methods
p !== 'constructor' && // not the constructor
(i == 0 || p !== arr[i - 1]) && // not overriding in this prototype
props.indexOf(p) === -1 // not overridden in a child
)
props = props.concat(propsAtCurrentDepth)
deep--
}
return props
}
例如: getMethods(new SubtractCalculator("name"));将返回 ["add", "subtract"]
您可以使用
decorator
模式动态向实例添加函数。希望它会被正式添加到 javascript 中,但是您可以使用实例是对象的事实来创建装饰器模式。编辑更改了代码以使用 Object.Assign
class Calculator {
constructor() {
this.name = "Calculator"
}
}
// decorator to create a super calculator that can add
const superCalculator = (calculator) => {
const add = (a,b) => a+b
return Object.assign(calculator, {name: "Super Calculator", add})
}
const simpleCalc = new Calculator();
const superCalc = superCalculator(new Calculator());
console.log(simpleCalc.name)
console.log(superCalc.name)
console.log(simpleCalc.add(3,4)) // simple calculator can not add
console.log(superCalc.add(3,4)) // super calculator can do it
这是一个解决方案(很抱歉切换到 TypeScript):
let operatorNames:Array<string> = ["add", "negate"];
let root:object = new Calculator("someName");
for (let operatorFunctionName of operatorNames) {
switch (operatorFunctionName) {
case "add":
let addPrototype = class AddClass {
add(left:number, right:number) {
console.log(this.name + " " + (left + right));
}
};
Object.setPrototypeOf(addPrototype.prototype, root);
root = new addPrototype();
break;
case "negate":
let negatePrototype = class NegateClass {
negate(right:number) {
console.log(this.name + " " + (-right));
}
};
Object.setPrototypeOf(negatePrototype.prototype, root);
root = new negatePrototype();
break;
}
}
return root as Calculator;
我们新建了一个基类 Calculator 的实例,它除了构造函数之外没有其他方法。然后我们将局部作用域中的派生类声明为局部变量(我尝试使用全局作用域类来执行此操作,但随后这些类被永久更改。)然后,我将局部类原型设置为将基类的实例作为原型.
然后我实例化派生类并将其存储为实例。然后重复使用更新后的实例。通过这种方式,我可以在派生类之上动态地不断添加派生类。
现在,这里有一些毛茸茸的地方,因为调用计算器构造函数的唯一方法是在创建派生类实例之前手动调用。我想我只有派生类的实例,而不是派生类本身(派生类本身无法实例化,因为不会调用计算器的构造函数。)但这没关系,因为我需要的一切是派生类的一个实例。
调用 getMethodNames(root) 会产生: ["add", "negate"] 正如预期的那样。
编辑:@Bergi 建议这更优雅并解决了我提到的构造函数问题。谢谢@Bergi!
let operatorNames:Array<string> = ["add", "negate"];
let root:object = Calculator;
for (let operatorFunctionName of operatorNames) {
switch (operatorFunctionName) {
case "add":
root = class AddClass extends root {
constructor(name) { super(name); }
add(left:number, right:number) {
console.log(this.name + " " + (left + right));
}
};
break;
case "negate":
root = class NegateClass extends root {
constructor(name) { super(name); }
negate(right:number) {
console.log(this.name + " " + (-right));
}
};
break;
}
}
return new root("someName") as Calculator;
人们可以选择基于继承和组合的方法,其中工厂函数基于例如类名(非强制)和基于函数的混合引用数组(强烈推荐),动态创建子类/子类型。
组合部分是通过将基于函数的 mixin 应用于子类构造函数的原型来完成的。继承是通过原型链覆盖的,可以直接通过子类的增强原型,也可以通过基类的链接原型。
如果涉及动态(或临时)创建基于
Calculator
的子类,其优点是不必编写太多样板(和重复)代码。
// - factory function which implements dynamic sub-classing (or sub-typing)
// - the special case of the below implementation is that just the created
// class constructor's prototype gets augmented by function-based mixins
// where each of the latter provides and applies a unique trait/behavior.
function createSubclassWithAugmentedPrototype(className, baseClass, listOfTraits) {
const subClass = ({
[className]: class extends baseClass {
constructor(...args) {
super(...args)
}
},
})[className];
listOfTraits
.forEach(trait => trait.call(subClass.prototype));
return subClass;
}
// function based mixins, each implementing a unique trait/behavior.
function withNegation() {
this.negate = function negate (right) {
return (-1 * right);
}
}
function withAddition() {
this.add = function add (left, right) {
return (left + right);
}
}
function withSubtraction() {
this.subtract = function subtract (left, right) {
return (left - right);
}
}
// base `Calculator` class
class Calculator {
constructor(name) {
this.name = name;
}
}
// helpers for calculator instance logs.
function getConstructorName(value) {
return Object.getPrototypeOf(value).constructor.name;
}
function getBaseConstructorName(value) {
return Object.getPrototypeOf(Object.getPrototypeOf(value)).constructor.name;
}
// - creation, access and logging of `Calculator` subclasses
// via e.g. an array based configuration and `map`/`forEach`.
[
['NegationType', [withNegation], 'negationOnly'],
['AdditionType', [withAddition], 'addOnly'],
['SubtractionType', [withSubtraction], 'subtractOnly'],
['NegationAdditionType', [withNegation, withAddition], 'negateAndAdd'],
['NegationSubtractionType', [withNegation, withSubtraction], 'negateAndSubtract'],
['AdditionSubtractionType', [withAddition, withSubtraction], 'addAndSubtract'],
['NegationAdditionSubtractionType', [withNegation, withAddition, withSubtraction], 'negateAndAddAndSubtract'],
]
.map(([className, listOfTraits, instanceName]) => [
createSubclassWithAugmentedPrototype(className, Calculator, listOfTraits),
instanceName,
])
.forEach(([CalculatorSubClass, instanceName]) => {
const calculatorSubType = new CalculatorSubClass(instanceName);
console.log({
instanceName: calculatorSubType.name,
className: getConstructorName(calculatorSubType),
baseClassName: getBaseConstructorName(calculatorSubType),
negate: calculatorSubType.negate,
add: calculatorSubType.add,
subtract: calculatorSubType.subtract,
"negate(-7)": calculatorSubType?.negate?.(-7),
"add(1,14)": calculatorSubType?.add?.(1, 14),
"subtract(19,10)": calculatorSubType?.subtract?.(19, 10),
});
});
.as-console-wrapper { min-height: 100%!important; top: 0; }