JavaScript uses a Prototype system, which is fundamentally different than a Class system. This is my first serious encounter with the language. I had fooled around with it previously, but this is the first time I built a system with proper OO, inheritance, polymorphism, etc.
From what I read there seems to be a few common methods to do member function inheritance in Javascript. Assuming you have a parent foo as following
foo = function(){ this.one = 1; }
foo.prototype.func1 = function(){return this.one;}
The MDN Introduction to JavaScript Inheritance suggests the naive approach of invoking the parent's method in the context of the child, as shown below.
bar = function(){ foo.call(this); }
bar.prototype = Object.create(foo.prototype);
bar.prototype.func1 = function(){ return this.one + foo.prototype.func1();}
This has the advantage of being simple to understand, but can become cumbersome as pointed out in this Salsify Blog post. The blog post outlines an alternate method where a super property is defined in the child prototype, and the name of each member function is attached as a property to the method. This method, however, relies on the caller property of a method, which the article points out will soon be deprecated. Rather than duplicate the entire post, I believe a summary of the important points are these
Object.defineProperty(bar.prototype, "super", {
get: function get() {
...
// methodName is set as a property on each method in an omitted code segment
methodName = get.caller.methodName;
...
Object.getPrototypeOf(this.prototype)[methodName]
}
}
Which is to say that you find the method with the same name in your prototype's prototype. I was wondering if this can be done in a simpler manner, without having to attach the method name as a parameter and without the Function.caller.
foo.prototype.super = function(method) {
superMethod = Object.getPrototypeOf(this.constructor.prototype)[method];
return superMethod.call(this, Array.prototype.slice.call(arguments, 1));
}
bar.prototype.func1 = function(){ return this.one + super('func1'); }
I'm making a number of assumptions in the above, I'd like to verify some assumptions.
- new bar().constructor.prototype === Object.getPrototypeOf(new bar())
- If the above is always true, is one preferable over the other?
- The Parent's member function will always live in the child's prototype's prototype (assuming that neither of the prototypes were mutated after object creation)
- That Object.getPrototypeOf() is not the "language support for accessing super methods" that the blog refers to as being added in ES6
- If Object.getPrototypeOf() isn't that language support, what is?
After seeing the error of using this, which does not change throughout the execution and always refers to the instance of the subclass, I've revisited and am thinking I need something like this
Grandfather = function(){};
Grandfather.prototype.function1 = function(){console.log("I am the Grandfather");};
Father = function(){Grandfather.apply(this);};
Father.prototype = Object.create(Grandfather.prototype);
Father.prototype.function1 = function f(){ f.super(); console.log("I am the Father");};
Father.prototype.function1.super = Grandfather.prototype.function1;
Child = function(){Father.apply(this);}
Child.prototype = Object.create(Father.prototype);
Child.prototype.function1 = function f(){ f.super(); console.log("I am the Child");};
Child.prototype.function1.super = Father.prototype.function1;
c = new Child();
c.function1();
// I am the Grandfather
// I am the Father
// I am the Child
And so the question becomes, how to set the super property on to each function in some automatic way?
One such way to do this is shown below, it has the benefit that functions added to the prototype after objects are instantiated still receive the benefit of being able to call superFunc, whereas an approach that sets a super property at class extension time would not set such a property if functions are added to the prototype later.
The downsides of this approach are that it only works in single threaded environment and that it requires functionality inherited from a common base class. It is not threadsafe since some state is held in a what is effectively a static variable of the function. This is fine for my purposes since browsers still have single threaded JavaScript. The requirement that all classes inherit from some base class containing this method isn't a huge blocker (especially if you do a "bad thing" and insert this into Object's prototype).
Grandfather.prototype.superFunc = function f(funcName){
currentPrototype = Object.getPrototypeOf(f.startingPrototype || Object.getPrototypeOf(this));
f.startingPrototype = currentPrototype;
return currentPrototype[funcName]();
}
Child.prototype.function2 = function(){this.superFunc('function2'); console.log("Still in the Child");};
Father.prototype.function2 = function(){this.superFunc('function2'); console.log("Still in the Father");};
GrandFather.prototype.function2 = function(){console.log("Still in the Grandfather");};
c = new Child();
c.function2();
// Still in the Grandfather
// Still in the Father
// Still in the Child
bar.prototype.func1 = function () { ... }? ;)