16

I have code like this

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

I want to look at all of the classes in my application's universe, and when I find one that descends from Animal, I want to create a new object of that type and add it to the list. This allows me to add functionality without having to update a list of things. So I can avoid the following:

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

PS: I don't want to add extra functionality to my classes or call them explicitly.

Donald Duck
  • 8,409
  • 22
  • 75
  • 99
obenjiro
  • 3,665
  • 7
  • 44
  • 82
  • you can push instances to a global collection from inside the constructor – dandavis Jul 24 '15 at 19:43
  • And who is going to call that constructor? I can write an extra line of code near each class definition, but this solution is far from "ideal" :/ And it's not too different from direct `animals.push` approach – obenjiro Jul 24 '15 at 19:46
  • well, you need to do it somewhere, and i think that on-creation (a centralized place) is going to be easier than searching and raking them all in later. – dandavis Jul 24 '15 at 19:49
  • the problem is that there will be thousands of those classes, I really don't want to do that by hand. And what if I forgot to add one? It could take a lot of time to realize that. Anyway I don't want to do that. PS: Any other language can do this easily.. TT – obenjiro Jul 24 '15 at 19:51
  • 2
    There is no way to "get all classes" in a JS application, so you'll either need to push the classes into a list and iterate over the classes checking for inheritance, or just create the instances when you define the class. – loganfsmyth Jul 24 '15 at 19:58
  • without instantiating and thus firing code in Animal's constructor, i don't see a way to tell by looking at Donkey that its tied to Animal. maybe it's just a traceur thing, but i don't see any "smoking gun"... – dandavis Jul 24 '15 at 20:06
  • @dandavis You definitely can: http://stackoverflow.com/questions/30993434/discover-if-a-constructor-inherits-another-in-es6 – loganfsmyth Jul 25 '15 at 01:24
  • @loganfsmyth: ahh, of course, same as es5's... thanks. – dandavis Jul 25 '15 at 07:41

4 Answers4

7

What about this:

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

I tried this in a TypeScript environment. The result:

Set(3) {"Dog", "Cat", "Donkey"}

without instantiating a class.

Denis Giffeler
  • 1,499
  • 12
  • 21
  • 3
    I would suggest using `this` instead of `this.name`. Depending on the scope a different Cat class could be used. But combining `static` with `this` and a set is a great solution. -- And on the Animal constructor you can easily check if the `this.constructor` is in the set to make sure that every extending class adds itself to the Set. – Christopher Nov 25 '21 at 09:43
  • 3
    Old question, but you can skip the dummy altogether and simply write : `static { Animal.derived.add(this.name); }` (you can have as many `static{}` as you want, too. They will execute in order. ) – Nebu Apr 22 '22 at 16:39
6

Here what I discovered so far 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

and the magic

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

The main disadvantage of this method is that I can not use ES6 module import and have to do old fashioned and simple contatenation of files, but this is still better that nothing.

PS: still wait for better answer

UPD: and ye, i know that to use this all classes must be defined globally, that's why i search for better way to do this..

obenjiro
  • 3,665
  • 7
  • 44
  • 82
  • 4
    In the general case, there is no guarantee that those constructors would be attached to `window` and in almost all standard modular JS code, they would not be. – loganfsmyth Jul 25 '15 at 01:26
  • 1
    …and in many JS applications, there's no `window` object at all. – Bergi Jul 25 '15 at 15:12
  • I didn't mean this needed to be fixed - as loganfsmyth observed, it likely doesn't work anyway in such environments. – Bergi Jul 25 '15 at 16:33
  • This is a hack that only works in the browser and only works if the subclasses have global scope. Without manually registering subclasses in some way so they can all be enumerated, there is no generic solution to this. – jfriend00 Jan 16 '19 at 04:45
6

I think you could make use of decorators. For instance, you could create @Extends() one and provide base class as an argument, e.g. @Extends(Animal). Inside the decorator function, you could take the name of the class decorated with @Extends and and put it into an array or an object. Don't know if it is applicable in browsers, but it should be. In Node with TypeScript I would do something like:

import { MyClassMetadata } from './';

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

Then you can access the MyClassMetadata variable that stores array of children of a given class and use it the way you want. You can play with it and get the desired result.

Sergey Orlov
  • 269
  • 4
  • 10
  • Why minus? Is this approach that bad? – Sergey Orlov Sep 14 '17 at 00:45
  • It wasn't me. Just saying .. anyway tnx for this solution. – obenjiro Sep 18 '17 at 12:14
  • 2
    possibly because decorators are not part of the js standard (yet?). you would need either typescript or a transpiler like babel – junvar Sep 20 '18 at 22:34
  • Assuming you have access to them (and yes, you'll have to use an extended language like TypeScript), decorators are the clean way to do this. They also have the advantage that they don't over-assume that one really wants *every* derivation of the class, which is a risky assumption to make; using decorators to tag derivations that should report they exist means devs are still free to create temporary, test, etc. derivations of Animal that won't get caught up in the list. – fixermark Nov 18 '19 at 16:30
3

It is not possible. You can never know e.g. about local classes defined inside some function, or privately in another module. And that's by design. It would be unmodular and break encapsulation.

Also, the set of classes is not static in JavaScript. You can create new classes dynamically open-endedly.

If you think you want such functionality then I strongly suggest you're holding it wrong. What are you trying to achieve?

Andreas Rossberg
  • 34,518
  • 3
  • 61
  • 72