What is the reason behind the inability to invoke a method when a class explicitly inherits from Object?

Issues arise when attempting to call an object method of a TypeScript class that explicitly extends Object:

class Example extends Object {
    constructor() {
        super();
    }
    public getHello() : string {
        return "Hello";
    }
}

let greeter = new Example();
alert(greeter.getHello());

An error is thrown:

greeter.getHello is not a function
. Why does this happen? When removing the extends clause and the super() call, the code suddenly works.

The complication stems from the fact that the code is generated from a customized JSweet version, where only certain parts of the codebase are transpiled. Due to constraints, classes in the class hierarchy that should not be transpiled are simply mapped to Object, as it's challenging to remove the extends without substantial modifications to JSweet.

Answer №1

One could argue that the behavior in TypeScript could be considered a bug, although perhaps not a critical one.

The issue arises because when Object is called, it ignores the context of this and instead returns a new empty object. In the case of TypeScript compiling code for ES5 environments, it results in the following call within the Example function:

function Example() {
    return Object.call(this) || this;
}

The result of Object.call(this) is essentially equivalent to creating an empty object with {}.

To work around this issue, it's advisable to avoid such constructs.

The challenge lies in the fact that the code is generated by a customized JSweet version, where only certain parts are transpiled. Classes that should not be transpiled are simply mapped to Object due to limitations in removing the extends clause without significant modifications to JSweet.

If targeting ES2015+ environment eliminates the problem since it specifically pertains to TypeScript output for ES5 and earlier versions. Alternatively, submitting an issue on the TypeScript issues list or contributing a fix via a pull request may be necessary if unable to upgrade. Unlike extending other built-in objects like Error or Array, resolving this issue is straightforward: ignoring the extends clause altogether.

Another approach, as mentioned in a comment, could involve creating a trivial class that does nothing:

class FauxObject { }

Using this class instead of Object (e.g., class Example extends FauxObject) can serve as a workaround.


For reference, the complete compiled version showcases the section marking the call to Object with ******:

var __extends = (this && this.__extends) || (function () {
    var extendStatics = function (d, b) {
        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 extendStatics(d, b);
    };
    return function (d, b) {
        extendStatics(d, b);
        function __() { this.constructor = d; }
        d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
    };
})();
var Example = /** @class */ (function (_super) {
    __extends(Example, _super);
    function Example() {
        return _super.call(this) || this;             // ******
    }
    Example.prototype.getHello = function () {
        return "Hello";
    };
    return Example;
}(Object));
var greeter = new Example();
alert(greeter.getHello());

In your scenario, _super represents Object.

Answer №2

Upon lodging a bug report, I was directed to a FAQ entry that discusses extending TypeScript built-ins:

In the realm of ES2015, constructors that yield an object automatically switch out the value of 'this' for any invokers of super(...). The generated constructor code must capture any potential return value from super(...) and replace it with 'this.'

Consequently, subclassing Error, Array, and similar entities may not function as anticipated. This is because constructor functions for Error, Array, and the like utilize ECMAScript 6's new.target to adjust the prototype chain; however, there is no surefire way to provide a value for new.target when invoking a constructor in ECMAScript 5. Other compilers that target earlier versions face a comparable limitation by default.

The explanation mirrors elements of T.J. Crowder's response, alongside an additional workaround they recommend:

As a suggestion, you can manually tweak the prototype right after any super(...) calls.

In my scenario, this translates to inserting

Object.setPrototypeOf(this, Example.prototype);
within the constructor (or persuading JSweet to take that action).

However:

Regrettably, these workarounds are ineffective on Internet Explorer 10 and previous iterations. One could manually transfer methods from the prototype onto the instance itself (e.g., FooError.prototype onto this), but rectifying the prototype chain proves unfeasible.

EDIT: Ultimately, I opted for this workaround due to its simplicity of integration into our JSweet project.

EDIT 2: Following some limitations of this workaround, we proceeded to employ a placeholder class as succinctly detailed in the acknowledged solution.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

When attempting to specify the path in the angular.json file, Angular encounters difficulty accessing Bootstrap from the node_modules directory

I have been attempting to integrate Bootstrap into my Angular project. Firstly, I used npm install --save bootstrap to add Bootstrap to my project. Following that, I installed jQuery as well. I then specified the path for Bootstrap. Displayed below is an ...

Trouble navigating through an index of elastic data? Learn how to smoothly scroll with Typescript in conjunction with

I'm currently using the JavaScript client for Elasticsearch to index and search my data, but I've encountered an issue with using the scroll method. Although I can't seem to set the correct index, I am confident in my technique because I am ...

TypeORM is failing to create a table upon initialization

Recently, I delved into the realm of typescript and decided to explore TypeORM for backend development. My current project involves building a CRUD API using typeORM and postgreSQL. After configuring everything initially, I ran the application only to rea ...

Issue with updating component

Currently facing an issue with a component that utilizes the changeDetection: ChangeDetectionStrategy.OnPush The component's logic is as follows: ngOnInit(){ this.serivce.something .subscribe( evt => { // Logic to update values of the ar ...

Add fresh data key and corresponding value into an array containing multiple objects

Inserting an accountId key and value into the this.membersForm.value object is the goal, while also setting the id to 0 and adding memberId: id to the this.membersForm.value.teamMembersDto object. How can we achieve this task of inserting the accountId ke ...

Formulate a Generic Type using an Enum

I'm currently working on a project that involves creating a generic Type using enums. Enum export enum OverviewSections { ALL = 'all', SCORE = 'score_breakdown', PERFORMANCE = 'performance_over_time', ENGAGEMENT ...

Jest is simulating a third-party library, yet it is persistently attempting to reach

Here's a function I have: export type SendMessageParameters = { chatInstance?: ChatSession, // ... other parameters ... }; const sendMessageFunction = async ({ chatInstance, // ... other parameters ... }: SendMessageParameters): Promise<vo ...

Tips for refining TypeScript discriminated unions by using discriminators that are only partially known?

Currently in the process of developing a React hook to abstract state for different features sharing common function arguments, while also having specific feature-related arguments that should be required or disallowed based on the enabled features. The ho ...

Angular: How to Disable Checkbox

Within my table, there is a column that consists solely of checkboxes as values. Using a for loop, I have populated all values into the table. What I have accomplished so far is that when a checkbox is enabled, a message saying "hey" appears. However, if m ...

After upgrading to Angular 15, the Router getCurrentNavigation function consistently returns null

Since upgrading to angular 15, I've encountered a problem where the this.router.getCurrentNavigation() method is returning null when trying to access a state property passed to the router. This state property was initially set using router.navigate in ...

A guide on converting JSON to TypeScript objects while including calculated properties

I have a standard JSON service that returns data in a specific format. An example of the returned body looks like this: [{ x: 3, y: 5 }] I am looking to convert this data into instances of a customized TypeScript class called CustomClass: export class ...

Discover the initial item in Observable that meets a certain criteria

I am trying to retrieve the first item of type avatar from payload.result within an observable: let result = this.fileService.getAvatarByUserId(2).pipe( map((payload: Payload<FileModel[]>) => payload.result), first((result: FileModel[]) => ...

What is the best way to find the common keys among elements in a tuple type?

type Tuple=[{a:string,x:string,z:string},{b:string,x:string,z:string}] type IntersectionOfTupleElementKeys<T>=... type MyType = IntersectionOfTupleElementKeys<Tuple> // = ('a'|'x'|'z')&('b'|'x&ap ...

The map function is calling an unresolved function or method named "map"

I'm encountering an error with the map method in my code, even after correctly importing 'rxjs/add/operator/map'. I've followed all the necessary steps and upgraded to rxjs 5.0.1, but the error persists. Do you have any suggestions on h ...

Enhancing Request JSON Body Validation in Next.js 14 API Handler

I'm currently in the process of developing an API for my iOS application using Next.js 14. I have managed to get it up and running successfully, however, I am facing some challenges when it comes to properly validating the body of a request. ESLint is ...

angular http fails to verify authorization header

My backend is set up in Node.js with Express and Sequelize. When I make a request to retrieve all my product types using Postman, everything works fine as shown in this image: postman http request and header However, when I try to make the same request f ...

The process of finding an element in an array using Typescript type guards necessitates first

After creating a type guard to verify if an object is of a specific type, I encountered a type error when trying to use array find with the type guard and a second condition. Strangely, the error disappears when I use array find with the type guard alone f ...

A TypeScript utility type that conditionally assigns props based on the values of other properties within the type

There is a common need to define a type object where a property key is only accepted under certain conditions. For instance, consider the scenario where a type Button object needs the following properties: type Button = { size: 'small' | &apo ...

TypeOrm is struggling to identify the entities based on the directory path provided

Currently, I am utilizing TypeORM with NestJS and PostgreSql to load entities via the datasource options object. This object is passed in the useFactory async function within the TypeORM module as shown below: @Module({ imports: [TypeOrmModule.forRootAsy ...

redux-toolkit extraReducers not triggering

I've been experimenting with createAsyncThunk, but I'm having trouble getting the extraReducers to trigger. This is what my current setup looks like: export const getAllAgents = createAsyncThunk( "getAllAgents", async (token: string ...