Is it possible to subclass and inherit from javascript Arrays?
I\'d like to have my own custom Array object that has all the features of an Array, but contains addit
Here's a full example that should work on ie9 and greater. For <=ie8 you'd have to implement alternatives to Array.from, Array.isArray, etc. This example:
If you can use ES6, you should use the class SubArray extends Array
method laggingreflex posted.
Here is the essentials to subclass and inherit from Arrays. Below this excerpt is the full example.
///Collections functions as a namespace.
///_NativeArray to prevent naming conflicts. All references to Array in this closure are to the Array function declared inside.
var Collections = (function (_NativeArray) {
//__proto__ is deprecated but Object.xxxPrototypeOf isn't as widely supported. '
var setProtoOf = (Object.setPrototypeOf || function (ob, proto) { ob.__proto__ = proto; return ob; });
var getProtoOf = (Object.getPrototypeOf || function (ob) { return ob.__proto__; });
function Array() {
var arr = new (Function.prototype.bind.apply(_NativeArray, [null].concat([].slice.call(arguments))))();
setProtoOf(arr, getProtoOf(this));
return arr;
}
Array.prototype = Object.create(_NativeArray.prototype, { constructor: { value: Array } });
Array.from = _NativeArray.from;
Array.of = _NativeArray.of;
Array.isArray = _NativeArray.isArray;
return { //Methods to expose externally.
Array: Array
};
})(Array);
Full example:
///Collections functions as a namespace.
///_NativeArray to prevent naming conflicts. All references to Array in this closure are to the Array function declared inside.
var Collections = (function (_NativeArray) {
//__proto__ is deprecated but Object.xxxPrototypeOf isn't as widely supported. '
var setProtoOf = (Object.setPrototypeOf || function (ob, proto) { ob.__proto__ = proto; return ob; });
var getProtoOf = (Object.getPrototypeOf || function (ob) { return ob.__proto__; });
function Array() {
var arr = new (Function.prototype.bind.apply(_NativeArray, [null].concat([].slice.call(arguments))))();
setProtoOf(arr, getProtoOf(this));//For any prototypes defined on this subclass such as 'last'
return arr;
}
//Restores inherited prototypes of 'arr' that were wiped out by 'setProtoOf(arr, getProtoOf(this))' as well as add static functions.
Array.prototype = Object.create(_NativeArray.prototype, { constructor: { value: Array } });
Array.from = _NativeArray.from;
Array.of = _NativeArray.of;
Array.isArray = _NativeArray.isArray;
//Add some convenient properties.
Object.defineProperty(Array.prototype, "count", { get: function () { return this.length - 1; } });
Object.defineProperty(Array.prototype, "last", { get: function () { return this[this.count]; }, set: function (value) { return this[this.count] = value; } });
//Add some convenient Methods.
Array.prototype.insert = function (idx) {
this.splice.apply(this, [idx, 0].concat(Array.prototype.slice.call(arguments, 1)));
return this;
};
Array.prototype.insertArr = function (idx) {
idx = Math.min(idx, this.length);
arguments.length > 1 && this.splice.apply(this, [idx, 0].concat([].pop.call(arguments))) && this.insert.apply(this, arguments);
return this;
};
Array.prototype.removeAt = function (idx) {
var args = Array.from(arguments);
for (var i = 0; i < args.length; i++) { this.splice(+args[i], 1); }
return this;
};
Array.prototype.remove = function (items) {
var args = Array.from(arguments);
for (var i = 0; i < args.length; i++) {
var idx = this.indexOf(args[i]);
while (idx !== -1) {
this.splice(idx, 1);
idx = this.indexOf(args[i]);
}
}
return this;
};
return { //Methods to expose externally.
Array: Array
};
})(Array);
Here are some usage examples and tests.
var colarr = new Collections.Array("foo", "bar", "baz", "lorem", "ipsum", "lol", "cat");
var colfrom = Collections.Array.from(colarr.reverse().concat(["yo", "bro", "dog", "rofl", "heyyyy", "pepe"]));
var colmoded = Collections.Array.from(colfrom).insertArr(0, ["tryin", "it", "out"]).insert(0, "Just").insert(4, "seems", 2, "work.").remove('cat','baz','ipsum','lorem','bar','foo');
colmoded; //["Just", "tryin", "it", "out", "seems", 2, "work.", "lol", "yo", "bro", "dog", "rofl", "heyyyy", "pepe"]
colmoded instanceof Array; //true
I've tried to do this sort of thing before; generally, it just doesn't happen. You can probably fake it, though, by applying Array.prototype
methods internally. This CustomArray
class, though only tested in Chrome, implements both the standard push
and custom method last
. (Somehow this methodology never actually occurred to me at the time xD)
function CustomArray() {
this.push = function () {
Array.prototype.push.apply(this, arguments);
}
this.last = function () {
return this[this.length - 1];
}
this.push.apply(this, arguments); // implement "new CustomArray(1,2,3)"
}
a = new CustomArray(1,2,3);
alert(a.last()); // 3
a.push(4);
alert(a.last()); // 4
Any Array method you intended to pull into your custom implementation would have to be implemented manually, though you could probably just be clever and use loops, since what happens inside our custom push
is pretty generic.
Checkout this. It works as it should in all browsers which support '__proto__'.
var getPrototypeOf = Object.getPrototypeOf || function(o){
return o.__proto__;
};
var setPrototypeOf = Object.setPrototypeOf || function(o, p){
o.__proto__ = p;
return o;
};
var CustomArray = function CustomArray() {
var array;
var isNew = this instanceof CustomArray;
var proto = isNew ? getPrototypeOf(this) : CustomArray.prototype;
switch ( arguments.length ) {
case 0: array = []; break;
case 1: array = isNew ? new Array(arguments[0]) : Array(arguments[0]); break;
case 2: array = [arguments[0], arguments[1]]; break;
case 3: array = [arguments[0], arguments[1], arguments[2]]; break;
default: array = new (Array.bind.apply(Array, [null].concat([].slice.call(arguments))));
}
return setPrototypeOf(array, proto);
};
CustomArray.prototype = Object.create(Array.prototype, { constructor: { value: CustomArray } });
CustomArray.prototype.append = function(var_args) {
var_args = this.concat.apply([], arguments);
this.push.apply(this, var_args);
return this;
};
CustomArray.prototype.prepend = function(var_args) {
var_args = this.concat.apply([], arguments);
this.unshift.apply(this, var_args);
return this;
};
["concat", "reverse", "slice", "splice", "sort", "filter", "map"].forEach(function(name) {
var _Array_func = this[name];
CustomArray.prototype[name] = function() {
var result = _Array_func.apply(this, arguments);
return setPrototypeOf(result, getPrototypeOf(this));
}
}, Array.prototype);
var array = new CustomArray(1, 2, 3);
console.log(array.length, array[2]);//3, 3
array.length = 2;
console.log(array.length, array[2]);//2, undefined
array[9] = 'qwe';
console.log(array.length, array[9]);//10, 'qwe'
console.log(array+"", array instanceof Array, array instanceof CustomArray);//'1,2,,,,,,,,qwe', true, true
array.append(4);
console.log(array.join(""), array.length);//'12qwe4', 11
class SubArray extends Array {
last() {
return this[this.length - 1];
}
}
var sub = new SubArray(1, 2, 3);
sub // [1, 2, 3]
sub instanceof SubArray; // true
sub instanceof Array; // true
Original Answer: (Not recommended, may cause performance issues)
Copy-pasting from article mentioned in the accepted answer for more visibility
__proto__
function SubArray() {
var arr = [ ];
arr.push.apply(arr, arguments);
arr.__proto__ = SubArray.prototype;
return arr;
}
SubArray.prototype = new Array;
Now you can add your methods to SubArray
SubArray.prototype.last = function() {
return this[this.length - 1];
};
Initialize like normal Arrays
var sub = new SubArray(1, 2, 3);
Behaves like normal Arrays
sub instanceof SubArray; // true
sub instanceof Array; // true
Juriy Zaytsev (@kangax) just today released a really good article on the subject.
He explores various alternatives like the Dean Edwards iframe borrowing technique, direct object extension, prototype extension and the usage of ECMAScript 5 accessor properties.
At the end there is no perfect implementation, each one has its own benefits and drawbacks.
Definitely a really good read:
I've created a simple NPM module that solves this - inherit-array. It basically does the following:
function toArraySubClassFactory(ArraySubClass) {
ArraySubClass.prototype = Object.assign(Object.create(Array.prototype),
ArraySubClass.prototype);
return function () {
var arr = [ ];
arr.__proto__ = ArraySubClass.prototype;
ArraySubClass.apply(arr, arguments);
return arr;
};
};
After writing your own SubArray
class you can make it inherit Array as follows:
var SubArrayFactory = toArraySubClassFactory(SubArray);
var mySubArrayInstance = SubArrayFactory(/*whatever SubArray constructor takes*/)