1

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?

ducin
  • 25,621
  • 41
  • 157
  • 256

2 Answers2

1

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).

Amit
  • 45,440
  • 9
  • 78
  • 110
  • 1
    I don't think this needs a new issue. There is [an existing issue](https://github.com/Microsoft/TypeScript/issues/7340) reporting this. The Playground is not as [configurable](https://github.com/Microsoft/TypeScript/issues/13673) as people would like; it targets ES5, and [you can't](http://perfectionkills.com/how-ecmascript-5-still-does-not-allow-to-subclass-an-array/) extend the Array constructor without using ES2015-and-up class syntax. – jcalz Jun 24 '18 at 16:42
  • @jcalz - good catch, and I'm honestly perplexed as to why this issue is closed without resolution. I could think of a workaround (hacky, but should work) or at the very minimum output a warning message to console (`if(_super.apply(...) !== this) { console.error('Unsupported base class...'); }`). – Amit Jun 24 '18 at 17:55
  • @Amit Neither `super(arguments)` nor `Reflect.construct(this, arguments)` compile under TypeScript. `super` has to be called (directly, nominally) and `arguments` of type `IArguments` violates my types. – ducin Jun 24 '18 at 22:30
  • @ducin - that part of my answer was confusing. I didn't mean a solution we developers could use, I meant what might work for the compiler. Edited answer accordingly... – Amit Jun 25 '18 at 04:36
  • @Amit no problem ;) thanks for your effort. The problem is the _potential_ solutions from last paragraph (`super(arguments)`, `Reflect.construct(this, arguments)`) don't compile. BTW see a working solution below, I think it's the most accurate one - addressing the explanation you gave) – ducin Jun 25 '18 at 08:53
0

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()
ducin
  • 25,621
  • 41
  • 157
  • 256