I would suggest you to use EventEmitter or, it's better descendent, Streams. Let's consider your example for a bit:
class CommonEmitter extends EventEmitter {}
Then, define a constant which is used by the base class as well as any other modules using it: const emitter = new CommonEmitter();.
Once we have that, you can emit different events based on the instantiation:
class Base {
constructor() {
emitter.emit( 'base-init' );
}
computeFoos() {
function* computeFoo( someParam ) {
yield someParam + 1;
yield someParam + 2;
}
emitter.emit( 'foo', computeFoo().next().value );
}
}
In your derived class:
class Sub extends Base {
constructor() {
super();
emitter.emit( 'sub-init' );
}
doStuff() {
super.computeFoos();
}
}
Then, you can use it like so:
emitter.once( 'base-init', () => {
emitter.on( 'foo', console.log );
} );
const myObject = new Sub();
myObject.doStuff();
// do something with sub here.
This is, of course, a very simple example. Since I do not know your exact use case, I can't advice any further without some information. Regardless, I hope this helps. :)