4

I have some experimental code that inherits from Array<T>...

class SuperArray<T> extends Array<T> {
    constructor(...items) {
        super(...items);
    }

    public clear(): void {
        while (this.length > 0) {
            this.pop();
        }
    }
}

var array = new SuperArray("Matt", "Mark", "Luke", "John");

array.clear();

Playground

I've added the clear method just to illustrate the problem. When this is compiled and run in the browser, I get...

TypeError: array.clear is not a function

How is this completely valid code in TypeScript, but not valid in JavaScript, and is there a way to fix this?

3 Answers 3

8

BTW this is a breaking change in TS2.1

This TS documentation has a recommendation for a changing the prototype in your constructor

constructor(...items) {
    super(...items);
    Object.setPrototypeOf(this, SuperArray.prototype);
}
Sign up to request clarification or add additional context in comments.

5 Comments

Strange...that makes my code "seem" like it's all working...what's actually going on here?
Also, is that why github.com/Microsoft/TypeScript/pull/12488 is coming in the 2.2 release?
You can read something here: github.com/Microsoft/TypeScript/issues/11919 It kind of worked in older TS versions but this caused other issues. #12488 is unrelated
Oh, this is nice, wasn't aware of that. Though it's kind of a hustle to do that for every subclass.
@NitzanTomer My guess is that __extends using Object.setPrototypeOf(...) will fix this in the 2.2 release.
2

Your code compiles into this when targeting es5:

var SuperArray = (function (_super) {
    __extends(SuperArray, _super);
    function SuperArray() {
        var items = [];
        for (var _i = 0; _i < arguments.length; _i++) {
            items[_i] = arguments[_i];
        }
        return _super.apply(this, items) || this;
    }
    SuperArray.prototype.clear = function () {
        while (this.length > 0) {
            this.pop();
        }
    };
    return SuperArray;
}(Array));
var array = new SuperArray("Matt", "Mark", "Luke", "John");
array.clear();

It's not possible to extend native objects like that, which is why you're getting this error.
If you'll target es6 then the compiled js will look like this:

class SuperArray extends Array {
    constructor(...items) {
        super(...items);
    }
    clear() {
        while (this.length > 0) {
            this.pop();
        }
    }
}
var array = new SuperArray("Matt", "Mark", "Luke", "John");
array.clear();

And that will work just fine (if you run it in a browser that supports es6 classes).


Edit

I'll just paste parts of the article Subclassing builtins in ECMAScript 6 which explains why it can't be done with es5:

Allocation obstacle: MyArray allocates the wrong kind of object
Array instances are special – the ECMAScript 6 specification calls them exotic. Their handling of the property length can’t be replicated via normal JavaScript. If you invoke the constructor MyArray then an instance of MyArray is created, not an exotic object.

Initialization obstacle: MyArray can’t use Array for initialization
It is impossible to hand an existing object to Array via this – it completely ignores its this and always creates a new instance.

3 Comments

What does ES6 do to allow this that ES5 cannot do (please feel free to be technical)
There's a good article about it: Subclassing builtins in ECMAScript 6
Revised my answer with quotes out of this article which explain why it can't be done with es5
0

Since Typescript 2.2 you can access new.target from the constructor.

Thus you can make the following call after having called super():

Object.setPrototypeOf(this, new.target.prototype);

It should solve the propotype chain issue

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.