The long path of JavaScript - from ES6 until today.
Tags: code, javascript, softwareengineering, learning
Published: 2024-05-25 10:10:00

---
title: 'The long path of JavaScript - from ES6 until today.'
description: 'JavaScript, the most popular programming language in 2023, has evolved from its early days in the 1990s to being used across various domains like web and mobile development, game development, and machine learning. How did a language developed in just 10 days by Brendan Eich achieve such widespread success?'
date: '2024/05/25'
tags: ['code', 'javascript', 'softwareengineering', 'learning']
authors:
  - 'Farid Shabanov'
disabled: false
published: true
published_at: '2024-05-25T10:10:00Z'
---

According to a [Stack Overflow survey](https://survey.stackoverflow.co/2023/#technology-most-popular-technologies), JavaScript was the most popular language among developers in 2023. JavaScript was initially developed for Netscape Navigator - a web browser that was developed in the middle of 1990s - and now is being used in almost every domain of programming - Web Development, Mobile app development, Game development, Machine Learning and many others.

But how did a language which was developed in 10 days by Brendan Eich become so popular? In this article, we will go through the life of JavaScript from ES6, which was released in 2015 and was the second major and the biggest release for the language, until today, the year of 2023. We will see why ES6 was important for the future of JavaScript, how it changed JavaScript and how it has influenced it over time.

## What was new and important in ES6?

ES6, also known as ECMAScript 2015, was the second major release for JavaScript after ES5 and was the largest release since the language was first released in 1997. It introduced several new features and syntax improvements that enhanced the language's functionality and made it more efficient.

Some of the key features introduced in ES6 include arrow functions, template literals, destructuring, and classes. These additions allowed developers to write cleaner and more concise code, improving readability and maintainability. The lack of some of these features was one of the main reasons why developers were choosing other languages over JavaScript. Another important point of this release was the further release schedule for JavaScript. According to the new schedule, a new version of JavaScript should be released each year which ensures the technology’s further development.

### Introduction of `let` and `const` keywords

One of the main features of ES6 was the introduction of the `let` and `const` keywords. You might wonder why these keywords were important if we already had `var`. The main difference between `var` and new keywords was that `var` has a global scope, while `let` and `const` have a block scope. Another important difference is that the variables declared with `var` are hoisted to the top of their scope (which is global).

The new keywords helped to solve a common issue with variables being accidentally redefined, which resulted in a big number of bugs.
The new `let` and `const` keywords are block scoped and they cannot be redefined. While the variable defined with `let` can be reassigned new values, the ones created with `const` can only be assigned once.

### JavaScript Modules

JavaScript modules were a crucial step in allowing the creation of big JavaScript applications. They allowed separating the code into different files, which would allow creating a cleaner codebase. In the early stages of JavaScript, it was used in only some web applications and only a small amount of JavaScript code was written. The pages were not very interactive and having modules was not necessary.

Over the years, different JavaScript libraries and packages were created and applications became more and more interactive, which required more JavaScript code. The lack of modules made this task harder and required additional packages, such as RequireJS.

### JavaScript Classes

Although it was possible to implement class-like functionalities before ES6 using `function`, it wasn’t very easy to do so. Before ES6, creating classes required updating the `prototype` object, which is a part of every object in JavaScript.

```tsx
// Before ES6
function User(name: string, birthYear: number): void {
  this.name = name;

  this.birthYear = birthYear;
}

User.prototype.calculateAge = function (): number {
  return new Date().getFullYear() - this.birthYear;
};

var user = new User('Farid', 2002);

// IDE does not see calculateAge method
console.log(user.calculateAge());
```

We needed to assign each method to a `prototype` object. Another disadvantage of this method is the fact that IDE does not see the methods we add to a `prototype` object. The alternative of the same object using ES6 `class` would be:

```tsx
// After ES6
class User {
  private name: string;

  private birthYear: number;

  public constructor(name: string, birthYear: number) {
    this.name = name;
    this.birthYear = birthYear;
  }

  public calculateAge(): number {
    return new Date().getFullYear() - this.birthYear;
  }
}

const user = new User('Farid', 2002);

// IDE sees calculateAge method
console.log(user.calculateAge());
```

As you can see, the new syntax makes creating classes much easier, faster and clear.

### Arrow functions

Arrow functions were another major addition to JavaScript with ES6. These functions allowed developers to write shorter and cleaner function expressions, and they also solved some issues with the `this` keyword in JavaScript.

In traditional function expressions (using `function` keyword), the `this` keyword would refer to the object that called the function. However, arrow functions capture the surrounding **`this`** context lexically, meaning that they inherit the **`this`** value from their surrounding scope. This eliminates the need for using **`.bind()`**, **`.call()`**, or **`.apply()`** to preserve the **`this`** context.

```jsx
class User {
  public name: string;

  public age: number;

  public constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
    // We have to bind `this` to be able to use its correct value within our method
    this.updateAge = this.updateAge.bind(this);
  }

  public updateAge(age: number) {
    this.age = age;
  }
}
```

In this example, `.bind` has to be called in order to have a correct value of `this` within our method. The same method would not require `.bind` to be called if the method is created using an arrow function.

```jsx
class User {
  public name: string;

  public age: number;

  public constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }

  public updateAge = (age: number) => {
    this.age = age;
  };
}

```

## What has changed since ES6?

Although ES6 was one of the biggest updates of JavaScript, it didn’t fix all the issues. Since ES6, lots of important features were added to JavaScript, which greatly improved the language.

### Async Await

The introduction of async functions made it much easier to work with Promises. By implementing asynchronous functions, we can wait for Promises to settle, before proceeding with the rest of the logic. Before the introduction of async functions, the same logic could be implemented by using `.then` chaining, which would reduce the readability of the code.

Here is an example of fetching the list of users without using `async await`:

```tsx
const getUsers = () => {
  fetchUsers()
    .then((response) => {
      return response.json();
    })
    .then((users) => {
      console.log(users);

      return users;
    });
};
```

And this is how simplified the same piece of code is when using `async await` syntax:

```tsx
const getUsers = async () => {
  const response = await fetchUsers();
  const users = await response.json();

  console.log(users);

  return users;
};
```

### Optional chaining

Optional chaining is a powerful feature, especially if we often access nested properties or functions, which are optional. It helps to avoid errors such as `Cannot read properties of undefined` or `Cannot read properties of null` by returning `undefined`when the property is not available.

```tsx
type Coordinates = {
  lat: number;
  lng: number;
};

type UserLocation = {
  country: string;
  coordinates?: Coordinates;
};

type User = {
  name: string;
  surname: string;
  location?: UserLocation;
};

// Without optional chaining
const getUserCoordinates = (user?: User): Coordinates | null => {
  if (user && user.location && user.location.coordinates) {
    return user.location.coordinates;
  }

  return null;
};
```

Without optional chaining, we need to check each nested object and make sure that the nested object exists. Optional chaining helps us to avoid unnecessary `if` checks and use inline checks.

```tsx
// With optional chaining
const getUserCoordinates = (user?: User): Coordinates | null => {
  return user?.location?.coordinates ?? null;
};
```

### Logical assignment operators

Starting from older versions of JavaScript, we can use different assignments such as `+=` or `-=`, but the similar assignments did not work for logical checks such as `||`, `&&` and `??`. The logical assignment operators assign the value on the right to the value on the left if the left value is falsy, truthy or nullish.

**Logical OR (`||=`)**

The logical OR operator only assigns the value if the value on the left is falsy. In case it is truthy, its value will not change.

```tsx
// Logical OR
let profilePictureUrl = '';

profilePictureUrl ||= 'default_url';

console.log(profilePictureUrl); // "default_url"
```

**Logical AND**

As opposed to the logical OR operator, the logical AND operator will only assign the value if the value on the left is truthy. This comes handy when we try to assign a value to a nested variable in an object.

```tsx
// Logical AND
type User = {
  name?: string;
};

const user: User = {};

user.name &&= 'Farid';
```

**Nullish coalescing assignment**

Nullish coalescing assignment is very similar to the logical OR assignment, but it will only assign a value if the left part is nullish. In JavaScript, nullish values are `null` and `undefined`

```tsx
// Nullish coalescing assignment
const user: User = {
  name: 'Farid',
};

user.name ??= 'Guest';
```

### Top level await

One of the most significant changes in JavaScript since ES6 is the introduction of top level await. This feature allows you to use the `await` keyword outside of async functions, at the top level of your code. It has made the handling of async operations more straightforward, especially in module initializations and configurations where async functions were not allowed before.

```tsx
import fs from 'fs/promises';

const readData = async () => {
  try {
    const content = await fs.readFile('data.txt', 'utf-8');

    return content;
  } catch (error) {
    throw new Error('Could not read file', { cause: error });
  }
};

const content = await readData();
```

Before, the same code would require using `.then` callbacks, which would make the code messy and hard to read

```tsx
import fs from 'fs/promises';

const readData = async () => {
  try {
    const content = await fs.readFile('data.txt', 'utf-8');

    return content;
  } catch (error) {
    throw new Error('Could not read file', { cause: error });
  }
};

readData().then((content) => {
  // handle content further
});
```

## What awaits JavaScript in future

JavaScript is improved day by day and a crucial role in this process is played by TC39 (Technical Committee 39). This committee is responsible for evolving the JavaScript language further, maintaining and updating the language standards, analyzing proposals and other processes that help JavaScript to continuously improve.
As mentioned before, TC39 is responsible for analyzing proposals. Proposals are the contributions from the community to add new features to JavaScript. Some of the popular proposals are `Temporal`, `import attributes` and `pipeline operator`.

### Temporal

[The Temporal API](https://github.com/tc39/proposal-temporal), which is currently in Stage 3, is being developed to improve the current Date object, which is mostly known for its unexpected behavior. Today there are lots of date-time libraries for JavaScript, such as `date-fns`, `moment`, `js-joda` and a huge number of others. They all try to help with unpredictable and unexpected behavior of JavaScript Date object by adding features such as timezones, date parsing and almost everything else.

With different objects like `Temporal.TimeZone`, `Temporal.PlainDate` and others, Temporal API is trying to address all these issues and replace JavaScript Date object. You can start testing the new API using an `npm` package named `@js-temporal/polyfill`

### Import attributes

[The proposal](https://github.com/tc39/proposal-import-attributes) for import attributes aims to improve the import statement in JavaScript by allowing developers to assert certain conditions about the imported module. This can help catch errors at compile-time instead of runtime and make the code more robust. Currently this proposal is in Stage 3.

With import attributes, you can specify the type of the imported module or ensure that a specific version of the module is used.

```tsx
import json from "./foo.json" assert { type: "json" };
import("foo.json", { with: { type: "json" } });
```

### Pipeline operator

[The pipeline operator](https://github.com/tc39/proposal-pipeline-operator) proposal, which is currently in Stage 2, introduces a new operator `|>` that allows developers to chain multiple function calls together in a more readable and concise way. Together with the pipeline operator, the placeholder operator `%` is being introduced which will hold the previous function’s value. It should enhance the readability and maintainability of code, especially when performing a series of operations on a value.

Instead of nested function calls or long chains of dot notation, the pipeline operator allows developers to write code in a more linear and intuitive manner. Here is a real-world example from a React repository, which can be improved by using the pipeline operator:

```tsx
console.log(
  chalk.dim(
    `$ ${Object.keys(envars)
      .map((envar) => `${envar}=${envars[envar]}`)
      .join(' ')}`,
    'node',
    args.join(' '),
  ),
);
```

As you can see, by adding nested function calls it becomes much harder to read and understand the code. By adding the pipeline operator, this code can be updated and improved:

```tsx
Object.keys(envars)
  .map(envar => `${envar}=${envars[envar]}`)
  .join(' ')
  |> `$ ${%}`
  |> chalk.dim(%, 'node', args.join(' '))
  |> console.log(%);
```

### Decorators

Although decorators are not yet available in JavaScript, they are actively used with the help of such transpilers as TypeScript, Babel and Webpack. Currently, [the decorators proposal](https://github.com/tc39/proposal-decorators) is in Stage 3, which means it is getting closer to being a part of native JavaScript. Decorators are the functions, that are called on other JavaScript elements, such as classes, class elements, methods, or other functions, by adding an additional functionality on those elements.

Custom decorators can be easily created and used. In the bottom, the decorator is just a function, that accepts specific arguments:

- Value - the element, on which the decorator is used
- Context - The context of the element, on which the decorator is used.

Here’s the total type of the decorator:

```tsx
type Decorator = (
  value: Input,
  context: {
    kind: string;
    name: string | symbol;
    access: {
      get?(): unknown;
      set?(value: unknown): void;
    };
    private?: boolean;
    static?: boolean;
    addInitializer(initializer: () => void): void;
  },
) => Output | void;
```

A useful example of using the decorators would be for protecting the methods with a validation rule. For that, we can create the following decorator:

```tsx
const validateUser = (rule: (...args: number[]) => boolean) => {
  const decorator: Decorator = (_target, _context) => {
    return function (...args: number[]) {
      const isValidated = rule(...args);

      if (!isValidated) {
        throw new Error('Arguments are not validated');
      }
    };
  };

  return decorator;
};
```

And use it the following way:

```tsx
const ADMIN_ID = 1;

const checkIsAdmin = (id: unknown): boolean => {
  return id === ADMIN_ID;
};

class User {
  @validateUser(checkIsAdmin)
  deleteUser(id: unknown) {
    // Delete logic
  }
}
```

These are just a few examples of the proposals that are being considered for the future of JavaScript. With the continuous efforts of TC39 and the active participation of the JavaScript community, we can expect JavaScript to evolve and improve even further in the coming years.

### Conclusion

JavaScript has come a long way since the release of ES6 in 2015. ES6 introduced important features such as `let` and `const` keywords, JavaScript modules, classes, and arrow functions. Since then, JavaScript has continued to improve, with the introduction of features like async/await, optional chaining, and logical assignment operators.

Looking ahead, JavaScript has a promising future with proposals like `Temporal`, `import` attributes, the `pipeline operator`, and `decorators`. With the continuous efforts of TC39 and the active participation of the JavaScript community, we can expect JavaScript to continue improving and remain a popular language in the years to come.