在Javascript应用程序中查找所有扩展基类的类

17

我有如下代码

class Animal{}
class Dog extends Animal {}
class Cat extends Animal {}
class Donkey extends Animal {}

我想查看应用程序宇宙中的所有类,当我找到一个继承自Animal的类时,我想创建一个该类型的新对象并将其添加到列表中。这使我能够添加功能而无需更新事物列表。因此,我可以避免以下情况:

var animals = [];
animals.push( new Dog() );
animals.push( new Cat() );
animals.push( new Donkey() );

PS:我不想为我的类添加额外的功能或显式地调用它们。


你可以在构造函数内部将实例推送到全局集合。 - dandavis
那么谁会调用那个构造函数呢?我可以在每个类定义附近编写额外的代码行,但这个解决方案远非“理想”:/ 而且它与直接使用 animals.push 方法并没有太大区别。 - obenjiro
问题在于会有成千上万个这样的类,我真的不想手动添加。如果我忘记添加一个怎么办?可能需要很长时间才能意识到。无论如何,我不想这样做。PS:任何其他语言都可以轻松完成这个任务。TT - obenjiro
2
在JS应用程序中没有办法“获取所有类”,因此您需要将类推入列表并迭代检查继承,或者在定义类时创建实例。 - loganfsmyth
@dandavis 你绝对可以:https://dev59.com/_F0Z5IYBdhLWcg3w3DQW - loganfsmyth
显示剩余3条评论
4个回答

8

这个怎么样:

class Animal {
  static derived = new Set();
}
class Dog extends Animal {
  static dummy = Animal.derived.add(this.name);
}
class Cat extends Animal {
  static dummy = Animal.derived.add(this.name);
}
class Donkey extends Animal {
  static dummy = Animal.derived.add(this.name);
}

console.log(Animal.derived);

我在TypeScript环境中尝试了这个。结果如下:
Set(3) {"Dog", "Cat", "Donkey"}

无需实例化一个类。


3
我建议使用 this 代替 this.name。根据作用域,可以使用不同的 Cat 类。但是将 staticthis 和一个 set 结合起来是一个很好的解决方案。在 Animal 构造函数中,您可以轻松地检查 this.constructor 是否在集合中,以确保每个扩展类都将自己添加到 Set 中。 - Christopher
3
旧问题,但是你可以完全跳过虚拟机,并简单地写出:static { Animal.derived.add(this.name); }(你可以有尽可能多的 static{},它们会按顺序执行。) - Nebu
如果Animal类使用泛型会怎么样?泛型不能在静态上下文中使用。 - undefined

6

我认为你可以利用装饰器。例如,您可以创建@Extends(),并将基类作为参数提供,例如@Extends(Animal)。在装饰器函数内部,您可以获取带有@Extends修饰符的类的名称,并将其放入数组或对象中。不知道它是否适用于浏览器,但应该是可行的。在使用TypeScript的Node中,我会这样做:

import { MyClassMetadata } from './';

export function Extends(parent): (...args: any[]) => void {
    return (target: object): void => {
        MyClassMetadata.someVariableToStoreChildren[parent.constructor.name].push(
            target,
        );
    }
}

然后,您可以访问存储特定类的子项数组的MyClassMetadata变量,并按照您的意愿使用它。您可以对其进行操作并获得所需结果。


为什么要减法?这种方法真的那么糟糕吗? - Sergey Orlov
不是我干的。只是这么说...不管怎样,谢谢你提供的解决方案。 - obenjiro
2
可能是因为装饰器还不是 JS 标准的一部分(尚未确定?)。您需要使用 TypeScript 或像 Babel 这样的转译器。 - junvar
假设您可以访问它们(是的,您将需要使用类似TypeScript的扩展语言),装饰器是实现此目的的干净方式。它们还具有一个优点,即它们不会过度假设一个人真的想要每个类的派生,这是一个冒险的假设;使用装饰器标记应报告其存在的派生意味着开发人员仍然可以创建临时、测试等Animal的派生,而不会被列入列表中。 - fixermark

6

以下是我目前发现的内容:http://jsbin.com/xiroyurinu/1/edit?js,console,output

class Animal{}
class Dog extends Animal {}
class Cat extends Animal {}
class Donkey extends Animal {}

var animals = getAllSubclasses(Animal);

console.log(animals.map(function(c){ return new window[c] })) // creates objects
document.body.innerText = animals; // Dog, Cat, Donkey

和魔术

function getAllSubclasses(baseClass) {
  var globalObject = Function('return this')(); 
  var allVars = Object.keys(globalObject);
  var classes = allVars.filter(function (key) {
  try {
    var obj = globalObject[key];
        return obj.prototype instanceof baseClass;
    } catch (e) {
        return false;
    }
  });
  return classes;
}

这种方法的主要缺点是我无法使用ES6模块导入,只能采用老式的文件拼接方式,但这仍然比没有好。请注意,为了使用此方法,所有类都必须在全局范围内定义,因此我正在寻找更好的解决方法。

4
通常情况下,并没有保证这些构造函数会被附加到 window,在几乎所有标准的模块化 JS 代码中,它们都不会被附加。 - loganfsmyth
1
在许多JS应用程序中,根本没有window对象。 - Bergi
我不是说这需要被修复 - 正如loganfsmyth所观察到的那样,在这种环境下可能根本不起作用。 - Bergi
这是一个只在浏览器中起作用的黑客技巧,只有子类具有全局范围时才能起作用。如果没有手动注册子类以便可以枚举所有子类,就没有通用解决方案。 - jfriend00

3

这是不可能的。你永远无法知道某些函数内部定义的局部类,或者在另一个模块中私有地定义的类。这是有意为之的。这样做会破坏模块化和封装性。

另外,在JavaScript中,类的集合是不静态的。你可以无限制地动态创建新的类。

如果您认为您需要这样的功能,我强烈建议您重新审视一下。您想实现什么目标?


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接