Three strange JavaScript classes that might be useful

English| https://javascript.plainenglish.io/3-weird-javascript-class-tricks-that-could-be-useful-c5a78144d574

Yang Xiaoai

I like to try new things, and JavaScript has many strange and hidden gems, which is always fun to learn.

Some are just vulnerabilities in the API, while others are intentional or unintentional accidents. Either way, as long as there are the right questions and the right needs, these are very useful.

1. Returns an object (singleton) from the constructor

It is very strange for a constructor to return anything other than an instance of the class to which it belongs. Well, in JavaScript, this is actually possible.

Let's take a simple example of cars.

class Car {
  make = '';
  model = '';
  doorsCount = 4;
  wheelsCount = 4;

  constructor(make, model) {
    this.make = make;
    this.model = model;
  }
}

By default, new Car(...) An instance object of a class will be returned, which we can use to access the properties and methods of the class.

const jeepWrangler = new Car('jeep', 'wrangler');
jeepWrangler.doorsCount = 2;
jeepWrangler.model; // wrangler

We can actually return something from the constructor, for example, a different object.

class Car {
  make = '';
  model = '';
  doorsCount = 4;
  wheelsCount = 4;

  constructor(make, model) {
    this.make = make;
    this.model = model;

    return {
      different: true
    }
  }
}

This just means that when we instantiate the class, we get the returned object.

const jeepWrangler = new Car('jeep', 'wrangler');
jeepWrangler.model; // undefined
jeepWrangler.different; // true

But this has this effect only when you return the object. If you return a primitive, the class will work properly.

class Car {
  make = '';
  model = '';
  doorsCount = 4;
  wheelsCount = 4;

  constructor(make, model) {
    this.make = make;
    this.model = model;

    return 12
  }
}
const jeepWrangler = new Car('jeep', 'wrangler');
jeepWrangler.model; // wrangler

So what can you do with this weirdness? Well, you can create a singleton: no matter how many times you instantiate it, you always get the same instance. The services provided by the Angular root are an example that can be done in singleton mode.

const LocalStore = (() => {
  const data = new Map();
  let instance = null;

  return class LocalStore {
    constructor() {
      if(instance === null) {
        instance = this;
      }
      return instance;
    }
  }
})();
const store1 = new LocalStore();
const store2 = new LocalStore();
store1 === store2 // true

Singletons apply to logging, analysis, databases, stored global classes, and alternative methods of creating global variables. You can use this technique to control the content returned by the class. 2. Prevent class instantiation (abstract class) JavaScript itself does not support the concept of abstract class, which is a class that can only be extended but not instantiated. However, there is a hacky method that does not include decorators to achieve this.

class Car {
  make = '';
  model = '';
  doorsCount = 4;
  wheelsCount = 4;

  constructor(make, model) {
    this.make = make;
    this.model = model;
  }
}
class BMW extends Car { <- extending Car
  constructor(model) {
    super('bmw', model);
    this.model = model;
  }
}
const bmwM3 = new Car('bmw', 'm3'); <- instantiate Car

We can take advantage of the fact that the class constructor name is accessed from within the class constructor.

class Car {
  make = '';
  model = '';
  doorsCount = 4;
  wheelsCount = 4;

  constructor(make, model) {
    this.make = make;
    this.model = model;

    console.log(this.constructor.name)
  }
}
const bmwM3 = new Car('bmw', 'm3');

The constructor name will always be the class you use to instantiate, which means we can use it to know whether the class is extended or instantiated.

class Car {
  make = '';
  model = '';
  doorsCount = 4;
  wheelsCount = 4;

  constructor(make, model) {
    this.make = make;
    this.model = model;

    if(this.constructor.name === 'Car') {
      throw new Error(
        'Car class is abstract. It can only be extended'
      )
    }
  }
}
class BMW extends Car {
  constructor(model) {
    super('bmw', model);
    this.model = model;
  }
}
const bmwM1 = new BMW('m1'); // works!!
const bmwM3 = new Car('bmw', 'm3'); // Throws

Therefore, if the constructor name matches the class you check for, instantiate the class directly. Otherwise, it will be instantiated by the class that extends it. Like this, you can create classes that can only be extended, which is very suitable for creating base classes. 3. Multiple class extensions at runtime (mixins) In OOP, you can make one class extend another, and you must do so before code execution. For some very specific problems, one thing I've been exploring in the code is the idea of extending classes at run time. To explain what I mean to you, I want you to imagine that you are playing a game and you have a Person class to represent your character. Let's use something as simple as this.

class Person {
  #name;
  #dob;

  constructor(name, dob) {
    this.#name = name;
    this.#dob = new Date(dob)
  }

  get name() {
    return this.#name;
  }

  get age() {
    return (new Date().getFullYear() - this.#dob.getFullYear())
  }
}
const johnDoe = new Person('John Doe', '09/12/1990');
johnDoe.age; // 32
johnDoe.name; // 'John Doe'

Suppose that this person is employed as a software engineer by AWS throughout the game. We can use the constructor to create a new function for this.

function Employee(company, startingDate, title) {
  this.occupation = {
    company,
    startingDate: new Date(startingDate),
    title,
  }
  this.quit = () => {
    delete this.occupation;
    delete this.quit;
  }
}

To give our users this capability, we can extend it at runtime like this:

Employee.call(johnDoe, 'AWS', '02/05/2020', 'software engineer');

With this, we can now obtain professional property and the ability to resign.

johnDoe.occupation;
// {company: 'AWS', startingDate: Wed Feb 05 2020 00:00:00 GMT-0500 (Eastern Standard Time), title: 'software engineer'}
johnDoe.quit();
johnDoe.occupation; // undefined
johnDoe.quit; // undefined

Of course, this is only a way to solve such problems. We can also try plug-in mode. What I want to illustrate is the ability to extend a class with multiple things. It is also a pre implementation of the base class, and starts from it. This is actually the way we used to extend classes before they were introduced into JavaScript. This class is just a syntax sugar for constructor and prototype work. The following classes can extend constructors.

class Person extends Employee {
  #name;
  #dob;

  constructor(name, dob, company, startingDate, title) {
    super(company, startingDate, title);
    this.#name = name;
    this.#dob = new Date(dob)
  }

  get name() {
    return this.#name;
  }

  get age() {
    return (new Date().getFullYear() - this.#dob.getFullYear())
  }

}

The above is the same, but it must be set before the code runs, and the Person class needs to accept more parameters.   The mixin approach allows you to split your code into smaller constructors that can handle all logic, including private data. This allows you to extend your class instances while the code is running. conclusion Exploring JavaScript is very interesting, which means that depending on how you twist and rotate, you may eventually find one or two techniques that may eventually prove useful. I used to think it was bad to take advantage of these things, but when I ventured into the world of metaprogramming, it became a habit to look for available gaps. I share these tips with you in the hope that one day they will be useful to you.

Added by PABobo on Sat, 19 Feb 2022 14:56:37 +0200