JavaScript has an OO quirk in that method calls in a superclass method will invoke subclass methods. I find I can work around this fairly easily except for constructors. The problem is that when constructing a subclass, the object is not available until super()
is called. Any methods called by the superclass constructor that are overriden in the subclass will find an object that has not been initialized by the subclass. Here's an example:
class Employee {
constructor (name, group) {
this.name = name;
this.setGroup(group);
}
setGroup (group) {
this.group = group;
}
}
class Manager extends Employee {
constructor (name, group, subordinates) {
super(name, group);
this.subordinates = subordinates.map(name => new Employee(name, group));
}
setGroup (group) {
super.setGroup(group);
this.subordinates.forEach(sub => sub.setGroup(group));
}
}
const mgr = new Manager('Fred', 'R&D', ['Wilma', 'Barney']);
This will fail in Employee.setGroup because this.subordinates has not been initialized.
One solution is to only call internal methods in the superclass constructor (e.g. _setGroup()), and provide public wrappers that can be overriden in the child. However, this is tedious as any methods called by the constructor can call other methods as well.
I came up with an alternative:
/**
* Call a function that executes methods from this class, bypassing any
* method in a subclass.
* @param {Function} ctor - A class or Function constructor
* @param {Object} self - An instance of the class
* @param {Function} fn - A function to call. "this" will be set to self. Any method
* calls on self will ignore overriding methods in any subclass and use the
* ctor's methods.
*/
/* exported useClassMethods */
function useClassMethods (ctor, self, fn) {
const subProto = Object.getPrototypeOf(self);
// temporarily set the object prototype to this (super)class
Object.setPrototypeOf(self, ctor.prototype);
try {
fn.call(self);
} catch (error) {
throw(error);
} finally {
// make sure the prototype is reset to the original value
Object.setPrototypeOf(self, subProto);
}
}
Used as follows:
class Employee {
constructor (name, group) {
useClassMethods(Employee, this, () => {
this.name = name;
this.setGroup(group);
})
}
setGroup (group) {
this.group = group;
}
}
This seems to work, but the neutrons are pretty hot in this part of the reactor and I'd like to know if anybody else has a better solution or can pick holes in it.