Develop a prototype function in ES6/ESNext with a distinct scope (avoid using an inline function)

Consider the following example:

class Car {
    constructor(name) {
        this.kind = 'Car';
        this.name = name;
    }

    printName() {
        console.log('this.name');
    }
}

My goal is to define printName using a different approach, like this:

class Car {
    constructor(name) {
        this.kind = 'Car';
        this.name = name;
    }

    // we aim to redefine printName with a different scope
    // the syntax is close, but not exactly correct
    printName: makePrintName(foo, bar, baz) 
}

where makePrintName is a functor, similar to this:

exports.makePrintName = function(foo, bar, baz){
   return function(){ ... }
};

Is this achievable with ES6? My editor and TypeScript are not approving of this

NOTE: Achieving this with ES5 was simple and looked like this:

var Car = function(){...};

Car.prototype.printName = makePrintName(foo, bar, baz);

Currently, the best solution using class syntax for me is:

const printName = makePrintName(foo, bar, baz);

class Car {
  constructor(){...}
  printName(){
    return printName.apply(this, arguments);
  }
}

However, this solution is not ideal. The limitations of using class syntax become evident when trying to achieve what was easily done with ES5 syntax. The ES6 class wrapper is, therefore, a leaky abstraction.

For a real-life use case, please refer to:

https://github.com/sumanjs/suman/blob/master/lib/test-suite-helpers/make-test-suite.ts#L171

The issue with using

TestBlock.prototype.startSuite = ... 
is that in that scenario, I cannot simply return the class on line:

https://github.com/sumanjs/suman/blob/master/lib/test-suite-helpers/make-test-suite.ts#L67

Answer №1

In JavaScript and TypeScript, the ideal methods for accomplishing this task may vary due to differences in the typing systems. If printName is intended to be a prototype method rather than an instance method (which is advantageous for several reasons), there are limited options available.

One approach is to retrieve the prototype method via accessor and preferably cache it to a variable.

const cachedPrintName = makePrintName(foo, bar, baz);

class Car {
    ...
    get printName(): () => void {
        return cachedPrintName;
    }
}

Alternatively, it can be lazily evaluated:

let cachedPrintName;

class Car {
    ...
    get printName(): () => void {
        return cachedPrintName || cachedPrintName = makePrintName(foo, bar, baz);
    }
}

Another option is to assign it directly to the class prototype. In this case, it should be typed as a class property to ensure TypeScript recognizes the assignment:

class Car {
    ...
    printName(): () => void;
}

Car.prototype.printName = makePrintName(foo, bar, baz);

Here, () => void represents the type of the function returned by makePrintName.

In TypeScript, a more natural approach is to extend the prototype chain and introduce new or modified methods using mixin classes. This adds complexity to the JavaScript code but satisfies TypeScript's type requirements:

function makePrintNameMixin(foo, bar, baz){
  return function (Class) {
    return class extends Class {
      printName() {...}
    }
  }
}

const Car = makePrintNameMixin(foo, bar, baz)(
  class Car {
    constructor() {...}
  }
);

Using TypeScript decorator is not straightforward in this scenario due to the lack of support for class mutation at the moment. To prevent type errors, the class should be supplemented with an interface:

interface Car {
  printName: () => void;
}

@makePrintNameMixin(foo, bar, baz)
class Car {
    constructor() {...}
}

Answer №2

Switch : with =

printName = makePrintName()

: is used for type notation.

Update
As mentioned in the comments, the above code will not alter the prototype. Instead, you can achieve this outside the class definition by using ES5 syntax:

// workaround: extracting function return type
const dummyPrintName = !true && makePrintName();
type PrintNameType = typeof dummyPrintName;

class Car {
    // ...
    printName: PrintNameType;
}
Car.prototype.printName = makePrintName();

Playground

Answer №3

One innovative approach that hasn't been discussed yet involves utilizing a method decorator. This particular decorator takes a method implementation and places it on the prototype as needed:

function setMethod<T extends Function>(value: T) {
    return function (target: any, propertyKey: string, descriptor: TypedPropertyDescriptor<T>): void {
      descriptor.value = value;
      delete descriptor.get;
      delete descriptor.set;
    };
}

Consider storing this decorator in a library for future use. Below is an example of how you can apply it:

class Car {
    constructor(name) {
        this.kind = 'Car';
        this.name = name;
    }

    @setMethod(makePrintName(foo, bar, baz))
    printName() {} // dummy implementation
}

One caveat is that you must include a placeholder implementation of the method with the correct signature in the class, as the decorator requires something to decorate. Nevertheless, this approach performs exactly as intended during runtime (it is not an instance method that incurs a new function definition per instance, nor an accessor that requires an additional function call each time it is used).

Does this offer a helpful solution?

Answer №4

Would you consider giving this a shot?

function House() {
    this.type = 'House';
    this.rooms = 3;
    this.color = 'green';
}

Answer №5

Am I missing something here?

The class keyword in TypeScript is essentially just a more convenient way of creating objects compared to the delegate prototype in ES5. I recently experimented with it and found it quite interesting.

class Vehicle {
private type: string;
private brand: string;
printBrand: Function;
    constructor(brand) {
                this.type = 'Vehicle';
                this.brand = brand;
            }

   }
var generatePrintBrand = function (foo, bar, baz) {
    return function () { console.log(foo, bar, baz); };
};
Vehicle.prototype.printBrand = generatePrintBrand('hi', 'there', 'how are you');
var toyota = new Vehicle('Toyota');
toyota.printBrand();
console.log(toyota.hasOwnProperty('printBrand'));

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

Mastering the art of efficiently handling jQuery AJAX in WordPress plugins

Struggling to implement ajax autosave on the settings page of my plugin, I created this code: <?php function cfgeo_settings_javascript() { ?> <script type="text/javascript" > (function($){ $(document).ready(function(){ ...

In what scenario would one require an 'is' predicate instead of utilizing the 'in' operator?

The TypeScript documentation highlights the use of TypeGuards for distinguishing between different types. Examples in the documentation showcase the is predicate and the in operator for this purpose. is predicate: function isFish(pet: Fish | Bird): pet ...

None of the Views are rendered after executing RedirectToAction

I created a Login Page that redirects to the required page after validation. However, when it redirects, the previous Login page view appears instead of the expected view. Below is the JavaScript code I am using: function abc() { var email = ...

Enhancing the TypeScript typings of modules in Angular 2

In my Angular2 application, I am facing an issue with an external NPM package that has an outdated typings file. This means there are functions within the package that are present but not declared in the typings file. My main goals are: To create and ut ...

Guide to dynamically resizing the Monaco editor component using react-monaco-editor

Currently, I am integrating the react-monaco-editor library into a react application for viewing documents. The code snippet below showcases how I have set specific dimensions for height and width: import MonacoEditor from 'react-monaco-editor'; ...

AngularJS SmartyUI position not dynamically updating on SmartyStreets plugin

In my angularJS application, I am utilizing SmartyStreets' LiveAddress for address validation and autofilling two fields in a form. After the user submits the form, a message (either success or failure) is displayed in a div above the form. However, t ...

A step-by-step guide to extracting live data using cheerio and socketio

I am currently scraping data from a website using Cheerio. Can someone provide me with guidance on how to retrieve web data and automatically update it using socket communication when there are changes on the website? I want to make this process real-tim ...

Transmit data via XMLHttpRequest in smaller portions or through a ReadableStream to minimize memory consumption when handling large datasets

Recently, I've been experimenting with JS's XMLHttpRequest Class for handling file uploads. Initially, I attempted to upload files using the following code: const file = thisFunctionReturnsAFileObject(); const request = new XMLHttpRequest(); req ...

Is it possible to refresh the Dictionary using data from localStorage?

I attempted to retrieve all key/value pairs from the localStorage and use them to update my dictionary named keywordDict in the following manner: $(document).ready(function() { var store = allStorage(); console.log(store); $.ajax({ url: '/m ...

How to retrieve the clicked value using jQuery within a jinja2 loop

I'm currently working on an app that utilizes a Flask backend and Jinja2 templating. Within the HTML, I have a loop set up to organize data into batches, creating three columns on the web page: {% for batch in df.iterrows() | batch(3) %} <d ...

Exporting components as the default from Next.js leads to a forced page refresh whenever there is a change

One issue I encountered involved having an index.tsx file containing all the shared UI components. When I exported the Footer and imported it into my shared Layout.tsx, which is utilized across all pages, the browser would refresh the page every time a cha ...

Connect the scroll wheel and then proceed to scroll in the usual way

There are two div elements with a height and width of 100%. One has the CSS property top:0px, while the other has top 100%. I have implemented an animation that triggers when the mousewheel is used to scroll from one div to the other. The animation functi ...

Best practices for making an AJAX call to fetch information from a database

I have a database containing a single table. The table includes columns for Company and Time, among others, with Company and Time being crucial. Users can make appointments by filling out a form. Within the form, there are 2 <select> elements - one ...

Using one payload.js file for all Nuxt static generated routes with identical data

I am facing a challenge with my NuxtJS site where I have only one page /pages/matrix/index.vue but numerous dynamic routes pointing to this page, all utilizing the same data set. During static build generation for deployment on Netlify, the size of the dis ...

The template literal expression is invalid due to the "string | null" type when sending authorization

While working on implementing authorization, I encountered an error from Ts-eslint stating that there was an "Invalid type 'string | null' of template literal expression" when trying to execute the functionality. The data being retrieved from lo ...

What is the best way to remove this .click function?

My goal is to create a switch function where clicking the "on" button changes its CSS, and if the user then clicks on the "off" button, the CSS returns to normal. I also need the switch to default to the "on" position, but my attempts so far have been unsu ...

Shift the sleek navigation arrows to the interior of the slides

Is there a way to relocate the navigation arrows on the slider images? I have tried various methods found online with no success. Currently using ASP.NET, but doubt it matters. Employing the latest version of SLICK. Here is the HTML code snippet: <%@ ...

I need to position a Vue component at the top of the page for a specific view only

How can I ensure that the component SiteHead only appears at the very top of the HomeView page, above the SiteNavigation component? If I place the SiteHead component within the HomeView.vue code, it ends up below SiteNavigation. Here is my App.vue code: & ...

Steps to properly specify the Express Error Type

When defining the variable err, I have opted to use any due to uncertainty about the correct type. I was anticipating an express.Error type, but none was found. What would be the appropriate way to assign a type to err? // Addressing Syntax Error in JSON ...

Resolving the Error: "Type 'Customer | undefined' is not compatible with type 'Customer'" in Angular

I encountered an issue with the following code: ... export class ListCustomersComponent implements OnInit { customers: Array<Customer> = []; showCustomer?: Customer; isSelected: boolean = false; deletedCustomer?: Customer; returnedMessa ...