ducin
ducin

Reputation: 26467

TypeScript online compiler _extends fails (doesn't actually extend)

Try out this link which includes following TS class that extends another class:

class ExtArray<T> extends Array<T> { 
    log() { 
        console.log(this)
    }
}

var a = new ExtArray(1,2,3)
a.log()

a.log should definitely exist, also, TS is able to compile that. However, the JS output fails to invoke ExtArray.prototype.log:

VM107:22 Uncaught TypeError: a.log is not a function
    at <anonymous>:22:3

The output:

var __extends = (this && this.__extends) || (function () {
    var extendStatics = Object.setPrototypeOf ||
        ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
        function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
    return function (d, b) {
        extendStatics(d, b);
        function __() { this.constructor = d; }
        d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
    };
})();
var ExtArray = /** @class */ (function (_super) {
    __extends(ExtArray, _super);
    function ExtArray() {
        return _super !== null && _super.apply(this, arguments) || this;
    }
    ExtArray.prototype.log = function () {
        console.log(this);
    };
    return ExtArray;
}(Array));
var a = new ExtArray(1, 2, 3);
a.log();

What is wrong?

Upvotes: 1

Views: 281

Answers (2)

Amit
Amit

Reputation: 46341

To start with, I strongly feel you've found a bug - please report it to typescript issues. (see jcalz's comment).

As for what's causing this, there's a technical explanation.

The Array function is "special" in various ways, and one of these is that the function behaves identically whether you invoke it directly or as a constructor (link):

When Array is called as a function rather than as a constructor, it also creates and initializes a new Array object. Thus the function call Array(…) is equivalent to the object creation expression new Array(…) with the same arguments.

Since TypeScript assumes that _super.apply(this, arguments) will return this but instead a new instance is returned, your a variable actually holds a pure Array instance, and not an ExtArray.

By the way, that special behavior I mentioned about Array is also true for other native objects, for example, RegExp.


As for Array(...) itself, it is meant to handle a super(...) call correctly, but since it is invoked indirectly via apply, it doesn't receive the correct new.target and that doesn't work.

A potential solution to this by the compiler might be to call super(arguments) or to reflect it with Reflect.construct(this, arguments).

Upvotes: 1

ducin
ducin

Reputation: 26467

I've found a working solution, basing on another SO thread solution. The trick is to add this line:

Object.setPrototypeOf(this, ExtArray.prototype);

to the constructor. All looks like this:

class ExtArray<T> extends Array<T> { 
    constructor(...args) { 
        super(...args)
        Object.setPrototypeOf(this, ExtArray.prototype);
    }
    log() { 
        console.log(this)
    }
}

var a = new ExtArray(1,2,3)
a.log()

Upvotes: 0

Related Questions