Matthew Layton
Matthew Layton

Reputation: 42390

Subclassing JavaScript arrays - constructor arguments not passing to array

I want to subclass a javascript Array and pass constructor arguments to the array. What I have is:

function SubArray(arguments) {
    Array.apply(this, arguments);
}

SubArray.prototype = Array.prototype;

Test does not indicate that arguments are being passed to the Array

var x = new SubArray("One", "Two", "Three");
// Object[]
// x.length = 0

Whereas when I do this with an Array I get this

var x = new Array("One", "Two", "Three");
// Object["One", "Two", "Three"]
// x.length = 3

What am I doing wrong?

Upvotes: 4

Views: 796

Answers (1)

Paul S.
Paul S.

Reputation: 66404

ES6

In ES6 you can extend Array using the class syntax

class SubArray extends Array {
    constructor() {
        super(...arguments);
    }
    first() {
        return this[0];
    }
}

length will work as expected

var sa = new SubArray('foo', 'bar');
sa[2] = 'baz';
sa.length; // 3
sa.first(); // "foo"
sa instanceof SubArray; // true

Pre-ES5

Up to and including ES5 there is no way to cleanly extend Array in the usual constructor-prototype way without losing functionality, instead you have to add properties to an Array instance

function SubArray() {
    var arr = Array.apply(null, arguments);
    arr.first = function () {return this[0];};
    return arr;
}

Using this no longer requires new as an Object is returned from the function, and instanceof will not be able to determine SubArray.

var sa = SubArray('foo', 'bar');
sa[2] = 'baz';
sa.length; // 3
sa.first(); // "foo"
sa instanceof SubArray; // false -- this gets broken

ES5 (slow)

In an ES5 environment where behaviour is more important than speed, you can force the prototype chain as desired using Object.setPrototypeOf (see MDN warning), this is "a bit of a hack" and would look like

function SubArray() {
    var arr = Array.apply(null, arguments);
    Object.setPrototypeOf(arr, SubArray.prototype);
    return arr;
}
SubArray.prototype = Object.create(Array.prototype);
SubArray.prototype.first = function () {return this[0];};

Again, new is no-longer required but this time the behaviour of instances is exactly as would be expected if it was extended normally.

var sa = SubArray('foo', 'bar');
sa[2] = 'baz';
sa.length; // 3
sa.first(); // "foo"
sa instanceof SubArray; // true

Upvotes: 6

Related Questions