Basically is there a good elegant mechanism to emulate super with syntax that is as simple as one of the following
this.$super.prop()
Here's my version: lowclass
And here's the super spaghetti soup example from the test.js file (EDIT: made into running example):
var SomeClass = Class((public, protected, private) => ({
// default access is public, like C++ structs
publicMethod() {
console.log('base class publicMethod')
protected(this).protectedMethod()
},
checkPrivateProp() {
console.assert( private(this).lorem === 'foo' )
},
protected: {
protectedMethod() {
console.log('base class protectedMethod:', private(this).lorem)
private(this).lorem = 'foo'
},
},
private: {
lorem: 'blah',
},
}))
var SubClass = SomeClass.subclass((public, protected, private, _super) => ({
publicMethod() {
_super(this).publicMethod()
console.log('extended a public method')
private(this).lorem = 'baaaaz'
this.checkPrivateProp()
},
checkPrivateProp() {
_super(this).checkPrivateProp()
console.assert( private(this).lorem === 'baaaaz' )
},
protected: {
protectedMethod() {
_super(this).protectedMethod()
console.log('extended a protected method')
},
},
private: {
lorem: 'bar',
},
}))
var GrandChildClass = SubClass.subclass((public, protected, private, _super) => ({
test() {
private(this).begin()
},
reallyBegin() {
protected(this).reallyReallyBegin()
},
protected: {
reallyReallyBegin() {
_super(public(this)).publicMethod()
},
},
private: {
begin() {
public(this).reallyBegin()
},
},
}))
var o = new GrandChildClass
o.test()
console.assert( typeof o.test === 'function' )
console.assert( o.reallyReallyBegin === undefined )
console.assert( o.begin === undefined )
Trying invalid member access or invalid use of _super will throw an error.
About the requirements:
this.$super must be a reference to the prototype. i.e. if I change the super prototype at run-time this change will be reflected. This basically means it the parent has a new property then this should be shown at run-time on all children through super just like a hard coded reference to the parent would reflect changes
No, the _super helper doesn't return the prototype, only an object with copied descriptors to avoid modification of the protected and private prototypes. Furthermore, the prototype from which the descriptors are copied from is held in the scope of the Class/subclass call. It would be neat to have this. FWIW, native classes behave the same.
this.$super.f.apply(this, arguments); must work for recursive calls. For any chained set of inheritance where multiple super calls are made as you go up the inheritance chain, you must not hit the recursive problem.
yep, no problem.
You must not hardcode references to super objects in your children. I.e. Base.prototype.f.apply(this, arguments); defeats the point.
yep
You must not use a X to JavaScript compiler or JavaScript preprocessor.
yep, all runtime
Must be ES5 compliant
Yes, it includes a Babel-based build step (f.e. lowclass uses WeakMap, which is compiled to a non-leaky ES5 form). I don't think this defeats requirement 4, it just allows me to write ES6+ but it should still work in ES5. Admittedly I haven't done much testing of this in ES5, but if you'd like to try it, we can definitely iron out any builds issues on my end, and from your end you should be able to consume it without any build steps.
The only requirement not met is 1. It would be nice. But maybe it is bad practice to be swapping out prototypes. But actually, I do have uses where I would like to swap out prototypes in order to achieve meta stuff. 'Twould be nice to have this feature with native super (which is static :( ), let alone in this implementation.
To double check requirement 2, I added the basic recursive test to my test.js, which works (EDIT: made into running example):
const A = Class((public, protected, private) => ({
foo: function (n) { return n }
}))
const B = A.subclass((public, protected, private, _super) => ({
foo: function (n) {
if (n > 100) return -1;
return _super(this).foo(n+1);
}
}))
const C = B.subclass((public, protected, private, _super) => ({
foo: function (n) {
return _super(this).foo(n+2);
}
}))
var c = new C();
console.log( c.foo(0) === 3 )
(the class header is a bit long for these little classes. I have a couple ideas to make it possible to reduce that if not all the helpers are needed up front)