2

I was previously rolling my own Javascript OOP but now I'm playing with ES6 and want to use the class defined after definition in a generic way.

Note Any answer with new in it is not what I'm after.

Pseudo code:

// base.js
class Base {
    constructor(arg) {
        this.arg = arg;
    }

    // This is the behaviour I'm after
    //afterDefined(cls) {
    afterExtended(cls) {    // probably a better name
        console.log(`Class name ${cls.prototype.name}`);
    }
}

// frombase.js
class FromBase extends Base {
    constructor({ p1='val1', p2='val2'} = {}) {
        super(...arguments);
        this.p1 = p1;
        this.p2 = p2;
    }
}

The output in the console should be:

'Class name FromBase'

So far the only solution I have come up with is to have a static method on Base and call it after the class declaration when I define a new class but I will most likely forget to do this more than once.

Just to be really thorough on why I don't like the static solution; it will force me to import Base in every single file.

Example using a static method (which I don't want) https://jsfiddle.net/nL4atqvm/:

// base.js
class Base {
    constructor(arg) {
        super(...arguments);
        this.arg = arg;
    }

    // This is the behaviour I'm after
    static afterExtended(cls) {
        console.log(`Class name ${cls.name}`);
    }
}

// frombase.js
class FromBase extends Base {
}
// just after defining the FromBase class
Base.afterExtended(FromBase);
Asken
  • 7,679
  • 10
  • 45
  • 77
  • wouldn't a call to `super()` do it? – Icepickle Jul 17 '17 at 08:53
  • @Icepickle Check out the fiddle. I'm not instantiating anything. – Asken Jul 17 '17 at 08:56
  • So instead the name of `afterDefined` would rather be `afterExtend`. And you want `afterExtend` to be called automatically if another class is defined that extends from `Base`. Right? – Christiaan Westerbeek Jul 17 '17 at 09:00
  • 2
    AFAIK this is not possible. There are no public hooks triggered when subclassing another class. – nils Jul 17 '17 at 09:00
  • 2
    I agree with @nils but further more, I don't have a clue why you want to implement this behavior. Why do you want to have this level of control? It doesn't make lots of sense from my point of view. What problem are you really trying to tackle here? – Icepickle Jul 17 '17 at 09:05
  • For some reason there is no universal way of enumerating classes defined. Running this generically would make it possible to populate a singleton class with class names and constructors automagically. It would make creating classes dynamically quite easy since all I ever pass is an object anyway and the constructor sets all defaults. – Asken Jul 17 '17 at 09:11
  • @ChristiaanWesterbeek Exactly! – Asken Jul 17 '17 at 09:11
  • 1
    Closest I could get to, is using a proxy on the Base class, but this won't give you proper naming (it would however trigger on each extend), as you can see in [this fiddle](https://jsfiddle.net/nL4atqvm/1/) – Icepickle Jul 17 '17 at 09:25
  • @Icepickle Thanks. I'll check that out and do some more digging. – Asken Jul 17 '17 at 09:28
  • I doubt it will do you any good, this is a terribly expensive way to do what you want to do... – Icepickle Jul 17 '17 at 09:29
  • Roger that. I'll just check the Proxy out for good measure :) – Asken Jul 17 '17 at 09:31

3 Answers3

2

There is no javascript built-in trigger that is calling a method on a class when a subclass is defined that extends from it.

Because you're rolling your own library, you could craft some kind of method the creates and returns a new class that extends a given base class. Maybe check out this answer that may help how to define your classes: Instantiate a JavaScript Object Using a String to Define the Class Name

You could also check how other javascript libraries creates (sub)classes. For example, Ext JS has a ClassManager that you could look into.

When this question would be about instantiation and not about defining classes, I would say:

afterDefined(cls) {
    console.log(`Class name ${this.constructor.name}`);
}

Usage:

let x = new FromBase()
x.afterDefined() // --> Class name FromBase

To get the name of the class, use

static afterDefined(cls) {
    console.log(`Class name ${this.name}`);
}
Christiaan Westerbeek
  • 10,619
  • 13
  • 64
  • 89
0

Is this what you're looking for?

class Base {
    constructor(arg) { this.arg = arg; }

    static afterDefined(cls) {
        console.log(`Class name ${this.constructor.name}`);
    }
}

Base = new Proxy(Base, {
    get: function(target, key, receiver) {
        if (typeof target == 'function' && key == 'prototype' && target.name == Base.name) {
            Reflect.apply(Base.afterDefined, Reflect.construct(class FromBase {}, []), [])
        }
        return target[key];
    }
});

class FromBase extends Base {}

In order for this to load class names, you will have to embed or forward-declare that information inside of the Proxy receiver prior to extending from it (which means you either need to enumerate ahead of time which classes you'll be inheriting to or to have some function call just prior to that class's declaration).

There are a bunch of other neat total hacks in the same vein such as looking at the source (text) of the file that you just loaded JavaScript from and then parsing that text as if it were JavaScript (people have used this to write Scheme interpreters that can accept new code inside of <script> tags).

If you are a library author intending to target Node, there are even more ways to go about doing this.

zetavolt
  • 2,989
  • 1
  • 23
  • 33
  • but you are assigning the constructor with `FromBase` in your `const klass` construction, is that what you want? – Icepickle Jul 17 '17 at 10:32
  • @Icepickle The constructor being assigned to `klass` is just a function named `FromBase`, the *real* `FromBase` isn't built by the time that line is run. As to why the constructor should be assigned to a fake function, the idea there is that the `receiver` can use that information in the scheme described above. – zetavolt Jul 17 '17 at 10:45
  • Although I suggested the proxy option in the comments, I really doubt this is a useful approach. I think your answer is inventive, but you are just replacing the call to the method with defining stuff in the proxy, which honestly, I think, doesn't really change things for the future. Lets say you make now a second class, how would your code change? – Icepickle Jul 17 '17 at 10:55
  • @Asken *"In order for this to load class names, you have to embed or forward declare that information inside of the Proxy receiver class prior to extending from it"* – zetavolt Jul 17 '17 at 22:01
  • Any chance you can uodate the code with 2 classes to see how that might work zv_? – Icepickle Jul 18 '17 at 05:19
  • @Icepickle You create a new proxy object or define a getter on `window`/`Base` for each superclass. If you are on Node you can use `Module._load` to generate these dynamically without making a new proxy object for each subclass (you might be able to do it on the browser with `eval` as well). Either way, you are totally correct that this is *not* good code and would be a huge footgun of an OOP library if implemented. – zetavolt Jul 18 '17 at 05:22
  • But I think@Asken rather wants to know about multiple subclasses, not superclasses. – Icepickle Jul 18 '17 at 05:24
  • Yes, that's correct. It should work with any subclass. – Asken Jul 18 '17 at 14:54
-1
// base.js

class Base {
constructor(arg) {
    this.arg = arg;
}

// This is the behaviour I'm after
afterDefined(cls) {
        console.log(`Class name ${cls}`);
    }
}


// frombase.js
class FromBase extends Base {
    constructor(arg) {
        super(arg)
    }
}


let f = new FromBase();
f.afterDefined('text');//this you text or object

have to be aware of is. file loading order, super is an instance of the parent class. good luck.