Reputation: 35321
I'm trying to subclass Array
, following the ideas and recommendations given in this article.
One important goal of this subclass of Array
, which here I'm calling ArrayBase
, is that it be itself more straightforward to subclass than Array
itself. I'm finding that achieving this goal is surprisingly difficult. (But it may look this way to me because I'm a JavaScript n00b!)
Below is an implementation of ArrayBase
that is based on the ideas presented towards the end of the article cited earlier, with some enhancements of my own. I've also included an implementation of ArrayBase.slice
, since it illustrates one problem with the scheme1.
function ArrayBase () {
var arr = Array.prototype.constructor.apply(null, arguments);
var ctor = arr.constructor = arguments.callee;
arr.__proto__ = ctor.prototype;
return arr;
}
ArrayBase.prototype = new Array;
ArrayBase.prototype.slice = function () {
var ctor = this.constructor;
return ctor.apply(null,
Array.prototype.slice.apply(this, arguments));
}
var a0 = new ArrayBase(0, 1, 2, 3);
var a1 = a0.slice(2); // [2, 3]
console.log(a1 instanceof ArrayBase); // true
console.log(a1 instanceof Array); // true
So far, so good. The problem happens when I now try to subclass ArrayBase
. I find that the only way to do this requires basically replicating the entire ArrayBase
constructor (the only difference, very slight, happens in the first line). As inheritance goes, this is pitiful...
function MyArray () {
var arr = ArrayBase.apply(this, arguments);
var ctor = arr.constructor = arguments.callee;
arr.__proto__ = ctor.prototype;
return arr;
}
MyArray.prototype = new ArrayBase;
// behavior of MyArray
var a2 = new MyArray(1, 2, 3, 0);
var a3 = a2.slice(1); // [2, 3, 0]
console.log(a3 instanceof MyArray); // true
console.log(a3 instanceof ArrayBase); // true
console.log(a3 instanceof Array); // true
console.log(a3.join(':')); // "2:3:0"
a3[5] = 1;
console.log(a3.length); // 6
a3.length = 2;
console.log(a3.toString()) // "2,3"
My questions:
How could the duplication that exists between the
ArrayBase
andMyArray
constructors, be eliminated, while still preserving the behavior illustrated by the lines after the// behavior of MyArr
line? Would the scheme work also at the time of subclassingMyArray
?
(I'm aware of the arguments against building tall towers of inheritance, but whether they are good design or not, I want them to be at least soundly implemented.)
1If inheritance from Array
were as I think it should be, it would not be necessary to implement ArrayBase.slice
, but unfortunately the slice
method that ArrayBase
inherits from Array
does not show the elementary OOP courtesy of returning an object of the same class as that of this
.
Upvotes: 2
Views: 205
Reputation: 664930
Before answering your question, some comments on the code :-)
var arr = Array.prototype.constructor.apply(null, arguments);
Since Array.prototype.constructor === Array
, don't duplicate this.
var ctor = arr.constructor = …
There's no reason to create a property here. If the constructor
property is needed for anything, it should be inherited from that constructor's .prototype
object.
arguments.callee;
Don't use deprecated arguments.callee
! You know that it points to ArrayBase
.
arr.__proto__ = ctor.prototype;
You probably know that __proto__
is nonstandard (and does especially not work in IE), but we need it here for the prototype injection technique. Still, don't forget this fact!
ArrayBase.prototype = new Array;
Do not use new
for setting up inheritance! You don't want to invoke an initialiser (i.e. "constructor") here. Use Object.create
instead.
Now, back to your question:
How can the duplication that exists between my
ArrayBase
andMyArray
constructors be eliminated?
Actually you have used that concept already. Your ArrayBase.prototype.slice
implementation works with every subclass - constructing instances of this.constructor
again. You can use the same method for the ArrayBase
constructor:
function ArrayBase() {
var arr = Array.apply(null, arguments);
var ctor = this.constructor;
arr.__proto__ = ctor.prototype;
return arr;
}
/* irrelevant for the answer, but helpful:
ArrayBase.prototype = Object.create(Array.prototype, {
constructor: {value: ArrayBase}
});
Object.keys(Array.prototype).forEach(function(k) {
if (typeof Array.prototype[k] != "function") return;
ArrayBase.prototype[k] = function() {
var arr = Array.prototype[k].apply(this, arguments);
if (Array.isArray(arr))
arr.__proto__ = Object.getPrototypeOf(this);
return arr;
};
});
*/
function MyArray() {
return ArrayBase.apply(this, arguments);
}
MyArray.prototype = Object.create(ArrayBase.prototype, {
constructor: {value: MyArray}
});
Upvotes: 2