The follow example works even if the "child" class already exists, and modifying the "base" class will still affect the child. Outside of the MergeClass function itself (which you could just copy/paste), you never have to write "prototype". And finally, it automatically stores the old functions into a global namespace variable, so using IIFE doesn't prevent micro-managing things down the line (but it does prevent temporary ES6 classes from clogging up global space). Only downside is the calls to old functions are still somewhat long winded (NAMESPACE.CLASS.FUNC.call(this)), but at least it retains compatibility with "super" for existing child classes. Also, don't know if it works with "get" functions, but those may be impossible to properly overwrite anyway.
I've tested this with both Node.JS (native ES6) and Babel (conversion to ES5). (PasteBin version with syntax colors)
Code:
// ====================================================
// * Base Class
// ====================================================
class Base {
test_return() {
return 0;
}
test_super() {
console.log("Base");
}
test_this() {
if(!this._val) this._val = 0;
return ++this._val;
}
}
// ====================================================
// * Child Class
// ====================================================
class Child extends Base {
test_return() {
return super.test_return() * 2;
}
test_super() {
super.test_super();
console.log("Child");
}
test_this() {
super.test_this();
return ++this._val;
}
}
/*** ---------------------------------------------------------------------------- ***/
// ====================================================
// * Namespace (to store old functions)
// ====================================================
var Dev = Dev || {};
Dev.PluginName = Dev.PluginName || {};
(function($) { // $ = Dev.PluginName
// ====================================================
// * Create Merge Class
// ====================================================
function MergeClass(classObj, es6Class, namespace) {
// Get Class Name as String (for ex: "Base")
const className = classObj.prototype.constructor.name;
// Add Class Name Property to Namespace
namespace[className] = {};
// Loop Through ES6 Prototype
Object.getOwnPropertyNames(es6Class.prototype).forEach(function(name) {
// Ignore Constructor
if(name === "constructor") return;
// If the Function Already Exists, Copy Into Namespace
if(classObj.prototype[name]) {
namespace[className][name] = classObj.prototype[name];
}
// Add/Overwrite Function Into Original Class
classObj.prototype[name] = es6Class.prototype[name];
});
}
// ====================================================
// * Modify Base Class
// ====================================================
class ModifiedBase {
test_return() {
return 12;
}
test_super() {
$.Base.test_super.call(this);
console.log("Base Modified");
}
test_this() {
$.Base.test_this.call(this);
return ++this._val;
}
}
// Add Content from ModifiedBase to Base
MergeClass(Base, ModifiedBase, $);
// ====================================================
// * Test Child Class
// ====================================================
const c = new Child();
// normally, this would be 0 since the Base.test_return is 0
// but since Base now returns 12, this prints 24
// base behavior modified while still retaining child behavior
console.log(c.test_return()); // prints 24
// this successfully prints the following (in order):
// Base
// Base Modified
// Child
c.test_super();
// normally, this would return 2, since it is incremented once in both Base and Child
// but now it increments by 3, since an increment is included in modified Base:
console.log(c.test_this()) // prints 3
console.log(c.test_this()) // prints 6
})(Dev.PluginName);