Is this a reasonable way to 'subclass' a javascript array?
I realize that, strictly speaking, this is not subclassing the array type, but will this work in the way one might expect, or am I still going to run into some issues with .length and the like? Are there any drawbacks that I would not have if normal subclassing were an option?
function Vector()
{
var vector = [];
vector.sum = function()
{
sum = 0.0;
for(i = 0; i < this.length; i++)
{
sum += this[i];
}
return sum;
} 开发者_如何学编程
return vector;
}
v = Vector();
v.push(1); v.push(2);
console.log(v.sum());
I'd wrap an array inside a proper vector type like this:
window.Vector = function Vector() {
this.data = [];
}
Vector.prototype.push = function push() {
Array.prototype.push.apply(this.data, arguments);
}
Vector.prototype.sum = function sum() {
for(var i = 0, s=0.0, len=this.data.length; i < len; s += this.data[i++]);
return s;
}
var vector1 = new Vector();
vector1.push(1); vector1.push(2);
console.log(vector1.sum());
Alternatively you can build new prototype functions on arrays and then just use normal arrays.
If you are consistent with naming the arrays so they all start with a lowercase v for example or something similar that clearly mark them aw vector and not normal arrays, and you do the same on the vector specific prototype functions, then it should be fairly easy to keep track of.
Array.prototype.vSum = function vSum() {
for(var i = 0, s=0.0, len=this.length; i < len; s += this[i++]);
return s;
}
var vector1 = [];
vector1.push(1); vector1.push(2);
console.log(vector1.vSum());
EDIT -- I originally wrote that you could subclass an Array just like any other object, which was wrong. Learn something new every day. Here is a good discussion
http://perfectionkills.com/how-ecmascript-5-still-does-not-allow-to-subclass-an-array/
In this case, would composition work better? i.e. just create a Vector object, and have it backed by an array. This seems to be the path you are on, you just need to add the push and any other methods to the prototype.
Nowadays you could use subclassing with ES6 classes:
class Vector extends Array {
sum(){
return this.reduce((total, value) => total + value)
}
}
let v2 = new Vector();
v2.push(1);
v2.push(2);
console.log(v2.sum());
console.log(v2.length);
v2.length = 0;
console.log(v2.length);
console.log(v2);
Just another example of the wrapper. Having some fun with .bind.
var _Array = function _Array() {
if ( !( this instanceof _Array ) ) {
return new _Array();
};
};
_Array.prototype.push = function() {
var apContextBound = Array.prototype.push,
pushItAgainst = Function.prototype.apply.bind( apContextBound );
pushItAgainst( this, arguments );
};
_Array.prototype.pushPushItRealGood = function() {
var apContextBound = Array.prototype.push,
pushItAgainst = Function.prototype.apply.bind( apContextBound );
pushItAgainst( this, arguments );
};
_Array.prototype.typeof = (function() { return ( Object.prototype.toString.call( [] ) ); }());
@hvgotcodes answer has an awesome link. I just wanted to summerize the conclusion here.
Wrappers. Prototype chain injection
This seems to be the best method to extend array from the article.
wrappers can be used ... in which object’s prototype chain is augmented, rather than object itself.
function SubArray() {
var arr = [ ];
arr.push.apply(arr, arguments);
arr.__proto__ = SubArray.prototype;
return arr;
}
SubArray.prototype = new Array;
// Add custom functions here to SubArray.prototype.
SubArray.prototype.last = function() {
return this[this.length - 1];
};
var sub = new SubArray(1, 2, 3);
sub instanceof SubArray; // true
sub instanceof Array; // true
Unfortunally for me, this method uses arr.__proto__
, unsupported in IE 8-, a browser I have to support.
Wrappers. Direct property injection.
This method is a little slower than the above, but works in IE 8-.
Wrapper approach avoids setting up inheritance or emulating length/indices relation. Instead, a factory-like function can create a plain Array object, and then augment it directly with any custom methods. Since returned object is an Array one, it maintains proper length/indices relation, as well as [[Class]] of “Array”. It also inherits from Array.prototype, naturally.
function makeSubArray() {
var arr = [ ];
arr.push.apply(arr, arguments);
// Add custom functions here to arr.
arr.last = function() {
return this[this.length - 1];
};
return arr;
}
var sub = makeSubArray(1, 2, 3);
sub instanceof Array; // true
sub.length; // 3
sub.last(); // 3
There is a way that looks and feels like prototypical inheritance, but it's different in only one way.
First lets take a look at one of the standard ways of implementing prototypical inheritance in javascript:
var MyClass = function(bar){
this.foo = bar;
};
MyClass.prototype.awesomeMethod = function(){
alert("I'm awesome")
};
// extends MyClass
var MySubClass = function(bar){
MyClass.call(this, bar); // <- call super constructor
}
// which happens here
MySubClass.prototype = Object.create(MyClass.prototype); // prototype object with MyClass as its prototype
// allows us to still walk up the prototype chain as expected
Object.defineProperty(MySubClass.prototype, "constructor", {
enumerable: false, // this is merely a preference, but worth considering, it won't affect the inheritance aspect
value: MySubClass
});
// place extended/overridden methods here
MySubClass.prototype.superAwesomeMethod = function(){
alert("I'm super awesome!");
};
var testInstance = new MySubClass("hello");
alert(testInstance instanceof MyClass); // true
alert(testInstance instanceof MySubClass); // true
The next example just wraps up the above structure to keep everything clean. And there is a slight tweak that seems at first glance to perform a miracle. However, all that is really happening is each instance of the subclass is using not the Array prototype as a template for construction, but rather an instance of an Array - so the prototype of the subclass comes hooked onto the end of a fully loaded object which passes the ducktype of an array - which it then copies. If you still see something strange here and it bothers you, I'm not sure that I can explain it better - so maybe how it works is a good topic for another question. :)
var extend = function(child, parent, optionalArgs){ //...
if(parent.toString() === "function "+parent.name+"() { [native code] }"){
optionalArgs = [parent].concat(Array.prototype.slice.call(arguments, 2));
child.prototype = Object.create(new parent.bind.apply(null, optionalArgs));
}else{
child.prototype = Object.create(parent.prototype);
}
Object.defineProperties(child.prototype, {
constructor: {enumerable: false, value: child},
_super_: {enumerable: false, value: parent} // merely for convenience (for future use), its not used here because our prototype is already constructed!
});
};
var Vector = (function(){
// we can extend Vector prototype here because functions are hoisted
// so it keeps the extend declaration close to the class declaration
// where we would expect to see it
extend(Vector, Array);
function Vector(){
// from here on out we are an instance of Array as well as an instance of Vector
// not needed here
// this._super_.call(this, arguments); // applies parent constructor (in this case Array, but we already did it during prototyping, so use this when extending your own classes)
// construct a Vector as needed from arguments
this.push.apply(this, arguments);
}
// just in case the prototype description warrants a closure
(function(){
var _Vector = this;
_Vector.sum = function sum(){
var i=0, s=0.0, l=this.length;
while(i<l){
s = s + this[i++];
}
return s;
};
}).call(Vector.prototype);
return Vector;
})();
var a = new Vector(1,2,3); // 1,2,3
var b = new Vector(4,5,6,7); // 4,5,6,7
alert(a instanceof Array && a instanceof Vector); // true
alert(a === b); // false
alert(a.length); // 3
alert(b.length); // 4
alert(a.sum()); // 6
alert(b.sum()); // 22
Soon we'll have class and the ability to extend native classes in ES6 but that may be a another year yet. In the mean time I hope this helps someone.
function SubArray(arrayToInitWith){
Array.call(this);
var subArrayInstance = this;
subArrayInstance.length = arrayToInitWith.length;
arrayToInitWith.forEach(function(e, i){
subArrayInstance[i] = e;
});
}
SubArray.prototype = Object.create(Array.prototype);
SubArray.prototype.specialMethod = function(){alert("baz");};
var subclassedArray = new SubArray(["Some", "old", "values"]);
精彩评论