🚀How JavaScript Works (Part 10)? Class
Tags: webdev, javascript, tutorial, programming
Published: 2023-10-26 10:29:23

JavaScript classes have become a fundamental part of modern JavaScript development. Introduced in ES6, they provide a convenient way to create and manage objects and their behaviors. However, understanding the full potential of JavaScript classes, beyond their syntactic sugar, is essential for writing efficient and maintainable code. In this article, we'll delve into JavaScript classes. We'll explore the syntax, inheritance, and the nuanced aspects of working with classes. We'll also discuss best practices, including how to avoid common pitfalls. Additionally, we'll take a closer look at the concept of auto-binding methods, which aims to address the challenge of maintaining the correct this context when working with class methods. Let's dive in!

## Table of Contents
- [The Class Syntax](#the-class-syntax)
- [Extending Classes](#extending-classes)
- [Relative Polymorphism](#relative-polymorphism)
- [Beyond Syntactic Sugar: A Closer Look](#beyond-syntactic-sugar-a-closer-look)
  - [Preserving `this`](#preserving-this)
  - [Understanding Prototype](#understanding-prototype)
- [The Challenge of Maintaining this Context](#the-challenge-of-maintaining-this-context)
- [The Concept of Auto-Binding](#the-concept-of-auto-binding)
- [The Problem with Auto-Binding Methods](#the-problem-with-auto-binding-methods)
  - [The Hacky Solution](#the-hacky-solution)
  - [Violating JavaScript's DNA](#violating-javascripts-dna)
- [The Danger of Permanent Hacks](#the-danger-of-permanent-hacks)
- [Best Practices: Embracing the Full Power of Classes](#best-practices-embracing-the-full-power-of-classes)
- [Conclusion](#conclusion)
- [Sources](#sources)

## The Class Syntax
In JavaScript, classes are defined using the class keyword. They can have names, but it's worth noting that classes can also be expressions and even anonymous. The class syntax supports the declaration of constructors and methods without the need for commas between them. For example:

```javascript
class Workshop {
    constructor(topic) {
        this.topic = topic;
    }

    ask(question) {
        console.log(`Welcome to the ${this.topic} workshop! ${question}`);
    }
}

const deepJS = new Workshop("JavaScript");
deepJS.ask("What happened to 'this'?");

```

## Extending Classes

JavaScript classes support inheritance through the extends clause. When a class extends another class, it inherits the methods and properties of the parent class. You can also define additional methods in the child class. Here's an example:
```javascript
class ChildWorkshop extends Workshop {
    speakUp() {
        console.log(`Speaking up in the ${this.topic} workshop!`);
    }
}

const childJS = new ChildWorkshop("Advanced JavaScript");
childJS.ask("What's the superclass?");
childJS.speakUp();

```

## Relative Polymorphism

Relative polymorphism is a concept that allows child classes to override methods defined in the parent `class`. JavaScript's `class` system supports this through the `super` keyword. It enables child `class`es to refer to methods in the parent `class` with the same name. This is particularly useful for customizing behavior. For instance:
```javascript
class ChildWorkshop extends Workshop {
    speakUp() {
        super.ask("Can I ask questions?");
        console.log(`Speaking up in the ${this.topic} workshop!`);
    }
}

```

## Beyond Syntactic Sugar: A Closer Look

### Preserving `this` <a name="preserving-this"></a>

One of the significant points is the behavior of `this` within `class` methods. In JavaScript, the `this` keyword is not auto-bound to `class` methods, and they behave just like regular functions. This means that if you pass a `class` method to a function like `setTimeout`, it will lose its this binding. To preserve this, developers often use hardbound methods or arrow functions, which can lead to unnecessary complexity and performance overhead.


### Understanding Prototype

The **class system** in JavaScript is built upon the concept of prototypes. Methods and properties are defined on the prototype, not on instances. However, when you assign a function directly to an instance, it no longer exists on the prototype. This practice can lead to creating separate copies of functions for each instance, which is inefficient and diverges from the core principles of class-based JavaScript.

## The Challenge of Maintaining this Context

One of the common challenges developers face when working with JavaScript classes is ensuring that the `this` context remains consistent when calling class methods. This is particularly crucial when you pass class methods as callbacks or use them in asynchronous operations like `setTimeout`. Without proper binding, the `this` context can become unpredictable.

Here's a simple example that illustrates this issue:

```javascript
class Workshop {
  constructor(teacher) {
    this.teacher = teacher;
  }

  ask(question) {
    console.log(`${this.teacher} asked: ${question}`);
  }
}

const deepJS = new Workshop("Kyle");
const askFunction = deepJS.ask;

askFunction("What is auto-binding?");

```

In this example, when we call `askFunction`, the `this` context inside the `ask` method is no longer bound to the `deepJS` instance. It results in an error because `this.teacher `is `undefined`.


## The Concept of Auto-Binding <a name="the-concept-of-auto-binding"></a>

Auto-binding methods refer to a mechanism that automatically maintains the correct this context for class methods, without the need for explicit binding. While JavaScript doesn't provide native auto-binding, developers often come up with solutions to achieve it.

Here's an example of how you can implement a basic auto-binding utility for class methods:
```javascript
function autoBindMethods(classInstance) {
  const prototype = Object.getPrototypeOf(classInstance);
  const methodNames = Object.getOwnPropertyNames(prototype);

  methodNames.forEach((methodName) => {
    if (typeof classInstance[methodName] === 'function') {
      classInstance[methodName] = classInstance[methodName].bind(classInstance);
    }
  });
}

class Workshop {
  constructor(teacher) {
    this.teacher = teacher;
    autoBindMethods(this); // Automatically bind class methods
  }

  ask(question) {
    console.log(`${this.teacher} asked: ${question}`);
  }
}

const deepJS = new Workshop("Kyle");
const askFunction = deepJS.ask;

askFunction("What is auto-binding?");

```
In this modified example, we use the `autoBindMethods` function to automatically bind all `class` methods when an instance is created. This way, the `ask` method maintains the correct `this` context even when used as `askFunction`.

## The Problem with Auto-Binding Methods <a name="the-problem-with-auto-binding-methods"></a>
### The Hacky Solution
In discussions about auto-bound methods, a potential solution has been suggested. The idea is to replace actual methods on class prototypes with getters. These getters would dynamically create hard-bound versions of the methods on the fly and cache them in a WeakMap. When you access a method through the getter, you automatically get a hard-bound version. This is a complex and unconventional approach that fundamentally changes the behavior of JavaScript functions.

### Violating JavaScript's DNA

JavaScript functions are known for their dynamic nature. Auto-binding methods, while seemingly convenient, contradict the fundamental principles of JavaScript's functions. Attempting to force JavaScript into the mold of classes from other languages leads to complex and hacky solutions like the one described above. It doesn't align with JavaScript's core philosophy of flexibility and dynamic behavior.

## The Danger of Permanent Hacks

As has been wisely noted, "There's nothing more permanent than a temporary hacker." The danger of introducing such hacky solutions is that they might become permanent parts of the language. Once developers start using them, it's challenging to reverse the trend, even if they contradict the core principles of the language.

## Best Practices: Embracing the Full Power of Classes

To make the most of JavaScript classes, it's crucial to embrace their full capabilities. Here are some best practices to consider:

Leverage Prototypes: Embrace the prototype chain and avoid assigning functions directly to instances. This ensures efficient memory usage and maintains the dynamic nature of classes.

Avoid Hardbound Methods: While hardbound methods and arrow functions can preserve this, they might not be necessary. Embrace the dynamic nature of JavaScript and leverage this without overcomplicating your code.

Keep Classes Dynamic: Don't limit the class system's dynamic flexibility. If you need dynamic, flexible structures, consider alternatives like the module pattern, which has been available for over two decades and provides a robust solution for many use cases.

## Conclusion
JavaScript classes are a powerful tool for creating and managing objects in your code. While they provide a convenient syntax, it's essential to understand their inner workings and nuances fully. In this article, we explored the syntax of JavaScript classes, their inheritance mechanism, and the concept of relative polymorphism using the super keyword. We also delved into the discussion surrounding auto-binding methods, which aim to address the challenge of maintaining the correct this context in class methods.

It's important to note that JavaScript's class system is built upon the concept of prototypes, where methods and properties are defined on the prototype rather than directly on instances. To make the most of JavaScript classes, we recommend leveraging prototypes, avoiding hardbound methods or arrow functions when they are unnecessary, and keeping classes dynamic.

While the idea of auto-binding methods has been considered, it's important to avoid overly complex solutions that contradict the core principles of JavaScript's dynamic behavior. By embracing the dynamic nature of JavaScript's class system and maintaining the flexibility it offers, you can write more efficient, maintainable, and robust JavaScript code.

Understanding JavaScript classes beyond their syntactic sugar empowers developers to make the most of this fundamental feature, creating better software and enhancing the JavaScript ecosystem.

## Sources
**Kyle Simpson's "You Don't Know JS"**
**MDN** Web Docs - The Mozilla Developer Network (MDN)