11 amazing Javascript features in ES13

Hi all. Nowadays JavaScript, like many other programming languages, is constantly evolving. Every year, new features are added to the language that allow developers to write more expressive and concise code.

Let’s take a look at the latest additions to ECMAScript 2022 (ES13), and see examples to get a better understanding of them.

1. Class Field Declaration

Before ES13, class fields could only be initialized in a special constructor() method. Unlike other languages, we could not declare or define them in the outermost field of a class.

class Person {
  constructor() {
    this.name = 'Alex';
    this.age = 22;
  }
}
const person = new Person();
console.log(person.name); // Alex
console.log(person.age); // 22
Enter fullscreen mode Exit fullscreen mode

ES13 removes this restriction. Now we can initialize fields without any method, like this:

class Person {
  name = 'Alex';
  age = 22;
}
const person = new Person();
console.log(person.name); // Alex
console.log(person.age); // 22
Enter fullscreen mode. Exit fullscreen mode

2. Private methods and fields
In many other languages, there are also «protected» fields, which are only available within a class or to child classes.

In the past it was impossible to declare private fields in a class because they were not implemented in JavaScript at the language level, but in practice they are very handy, so they are emulated. Most often private fields have an underscore prefix at the beginning of the name. Thereby indicating that it is closed, but could still be accessed and changed from outside the class.

class Person {
  _firstName = 'Ivan';
  _lastName = 'Sidorov';
  get name() {
    return `${this._firstName} ${this._lastName}`;
  }
}
const person = new Person();
console.log(person.name); //  Ivan Sidorov
// Участники, которые должны быть приватными, все еще доступны
// вне класса

console.log(person._firstName); // Ivan
console.log(person._lastName); // Sidorov

// Здесь мы можем изменить участника
person._firstName = 'Oleg';
person._lastName = 'Ivanov';
console.log(person.name); // Oleg Ivanov
Enter fullscreen mode Exit fullscreen mode

With ES13, we can now add private fields to the class by adding a hashtag (#) to them. Now any attempt to access them from outside the class will result in an error:

class Person {
  #firstName = 'Ivan';
  #lastName = 'Sidorov';
  get name() {
    return `${this.#firstName} ${this.#lastName}`;
  }
}
const person = new Person();
console.log(person.name);
// SyntaxError: Private field '#firstName' must be
// declared in an enclosing class
console.log(person.#firstName);
console.log(person.#lastName);
Enter fullscreen mode Exit fullscreen mode

Note that the error called here is a syntax error that occurs at compile time, so the code below will not run. The compiler doesn’t expect you to try to access private fields from outside the class, so it assumes you’re trying to declare one of them.

3. Await at the top level.
In JavaScript, the await operator is used to suspend the execution of an asynchronous function and wait until the promise is fulfilled or rejected.

Previously, we could only use this operator in an asynchronous function, a function declared with the keyword async. We could not do this globally.

function setTimeoutAsync(timeout) {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve();
    }, timeout);
  });
}
// SyntaxError: await is only valid in async functions
await setTimeoutAsync(3000)
Enter fullscreen mode Exit fullscreen mode

With ES13 we can now do this:

function setTimeoutAsync(timeout) {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve();
    }, timeout);
  });
}
// Ожидает таймаут и ошибка не вызывается
await setTimeoutAsync(3000);
Enter fullscreen mode Exit fullscreen mode.

4. Static (private) properties and methods
We can now declare static fields and static private methods for the class in ES13. Static methods can access other private/public static elements of the class with the this keyword, and instance methods can access them with this.constructor.

class Person {
  static #count = 0;
  static getCount() {
    return this.#count;
  }
  constructor() {
    this.constructor.#incrementCount();
  }
  static #incrementCount() {
    this.#count++;
  }
}
const person1 = new Person();
const person2 = new Person();
console.log(Person.getCount()); // 2
Enter fullscreen mode Exit fullscreen mode.

5. Static class block
ES13 allows you to define static blocks which will only be executed once, when the class is created. This is similar to static constructors in other languages that support object-oriented programming, such as C# or Java.

A class can have any number of static static {} blocks in its class body. They will be executed along with any interleaved static field initializers in the order in which they are declared. We can also use the super property in the static block to access parent class properties.

class Vehicle {
  static defaultColor = 'blue';
}
class Car extends Vehicle {
  static colors = [];
  static {
    this.colors.push(super.defaultColor, 'red');
  }
  static {
    this.colors.push('green');
  }
}
console.log(Car.colors); // [ 'blue', 'red', 'green' ]
Enter fullscreen mode Exit fullscreen mode.

6. Checking for private elements
Using the in operator we can use this new function to check if the object has a private field.
It provides a compact way to check if an object has a private field.

class Car {
  #color;
  hasColor() {
    return #color in this;
  }
}
const car = new Car();
console.log(car.hasColor()); // true;
Enter fullscreen mode Exit fullscreen mode

The in operator can distinguish private fields with the same name from different classes:

class Car {
  #color;
  hasColor() {
    return #color in this;
  }
}
class House {
  #color;
  hasColor() {
    return #color in this;
  }
}
const car = new Car();
const house = new House();
console.log(car.hasColor()); // true;
console.log(car.hasColor.call(house)); // false
console.log(house.hasColor()); // true
console.log(house.hasColor.call(car)); // false
Enter fullscreen mode Exit fullscreen mode

7. The at() method
We usually use square brackets ([]) in JavaScript to access the N element of an array.

const arr = ['a', 'b', 'c', 'd'];
console.log(arr[1]); // b
Enter fullscreen mode Exit fullscreen mode

To access the Nth element from the end of the array in square brackets we must use the following index arr.length - N

const arr = ['a', 'b', 'c', 'd'];
// 1ый элемент с конца
console.log(arr[arr.length - 1]); // d
// 2ой элемент с конца
console.log(arr[arr.length - 2]); // c
Enter fullscreen mode Exit fullscreen mode

A new method at() allows us to do this more succinctly. To access the N element from the end of the array, we simply pass a negative value of -N into at().

const arr = ['a', 'b', 'c', 'd'];
// 1ый элемент с конца
console.log(arr.at(-1)); // d
// 2ой элемент с конца
console.log(arr.at(-2)); // c
Enter fullscreen mode Exit fullscreen mode

String and TypedArray have this property, along with an array.

const str = 'Hello World';
console.log(str.at(-1)); // d
console.log(str.at(-2)); // l
const typedArray = new Uint8Array([16, 32, 48, 64]);
console.log(typedArray.at(-1)); // 64
console.log(typedArray.at(-2)); // 48
Enter fullscreen mode Exit fullscreen mode

8. RegExp matching indexes
This new function allows us to specify that we want to get both the initial and final match indexes of the RegExp object in a given string.

Previously, we could only get the start index of a regular expression match in a string.

const str = 'sun and moon';
const regex = /and/;
const matchObj = regex.exec(str);
// [ 'and', index: 4, input: 'sun and moon', groups: undefined ]
console.log(matchObj);
Enter fullscreen mode Exit fullscreen mode

Now we can set the d flag for a regular expression to get two indexes where the match begins and ends.

const str = 'sun and moon';
const regex = /and/d;
const matchObj = regex.exec(str);
/**
[
  'and',
  index: 4,
  input: 'sun and moon',
  groups: undefined,
  indices: [ [ 4, 7 ], groups: undefined ]
]
 */
console.log(matchObj);
Enter fullscreen mode Exit fullscreen mode

With the d flag set the return object will have a indices property containing the start and end indexes.

9. Object.hasOwn() method
In JavaScript we can use the Object.prototype.hasOwnProperty() method to check if the object has the given property.

class Car {
  color = 'green';
  age = 2;
}
const car = new Car();
console.log(car.hasOwnProperty('age')); // true
console.log(car.hasOwnProperty('name')); // false
Enter fullscreen mode Exit fullscreen mode

But there are some problems with this approach. First of all, the Object.prototype.hasOwnProperty() method is not protected, and can be overridden by defining a custom hasOwnProperty() method for the class, which can have a completely different behavior than Object.prototype.hasOwnProperty().

class Car {
  color = 'green';
  age = 2;
  hasOwnProperty() {
    return false;
  }
}
const car = new Car();
console.log(car.hasOwnProperty('age')); // false
console.log(car.hasOwnProperty('name')); // false
Enter fullscreen mode Exit fullscreen mode

Another problem is that for objects created using a null prototype (using Object.create(null)<###code>), trying to call this method will cause an error.

const obj = Object.create(null);
obj.color = 'green';
obj.age = 2;
// TypeError: obj.hasOwnProperty is not a function
console.log(obj.hasOwnProperty('color'));
Enter fullscreen mode Exit fullscreen mode

One way to solve these problems is to use the call() method to Object.prototype.hasOwnProperty Function property, for example:

const obj = Object.create(null);
obj.color = 'green';
obj.age = 2;
obj.hasOwnProperty = () => false;
console.log(Object.prototype.hasOwnProperty.call(obj, 'color')); // true
console.log(Object.prototype.hasOwnProperty.call(obj, 'name')); // false
Enter fullscreen mode Exit fullscreen mode

This is not very convenient. We can write a reused function so we don't have to repeat it:

function objHasOwnProp(obj, propertyKey) {
  return Object.prototype.hasOwnProperty.call(obj, propertyKey);
}
const obj = Object.create(null);
obj.color = 'green';
obj.age = 2;
obj.hasOwnProperty = () => false;
console.log(objHasOwnProp(obj, 'color')); // true
console.log(objHasOwnProp(obj, 'name')); // false
Enter fullscreen mode Exit fullscreen mode

However this is not necessary because we can use the new built-in Object.hasOwn() method. Like our reused function, it takes an object and a property as arguments and returns true value if the specified property is a direct property of the object. Otherwise it returns false.

const obj = Object.create(null);
obj.color = 'green';
obj.age = 2;
obj.hasOwnProperty = () => false;
console.log(Object.hasOwn(obj, 'color')); // true
console.log(Object.hasOwn(obj, 'name')); // false
Enter fullscreen mode Exit fullscreen mode

10. Error Cause
Error objects now have a cause property to indicate the original error that caused the error. This helps add additional information to the error and helps diagnose unexpected behavior. We can indicate the cause of the error by setting the cause property of the object passed to the Error() constructor as a second argument.

function userAction() {
  try {
    apiCallThatCanThrow();
  } catch (err) {
    throw new Error('New error message', { cause: err });
  }
}
try {
  userAction();
} catch (err) {
  console.log(err);
  console.log(`Cause by: ${err.cause}`);
}
Enter fullscreen mode Exit fullscreen mode

11. Array search
In JavaScript we can use the Array.find() method to find an element in the array that passes the given test condition. Similarly, we can use findIndex() to find the index of such an element. While find() and findIndex() both start searching from the first element of the array, there are cases where it would be preferable to start searching from the last element.

There are scenarios where we know that searching on the last element can improve performance. For example, here we are trying to retrieve an element in an array with value='y'. With find() and findIndex():

const letters = [
  { value: 'v' },
  { value: 'w' },
  { value: 'x' },
  { value: 'y' },
  { value: 'z' },
];
const found = letters.findLast((item) => item.value === 'y');
const foundIndex = letters.findLastIndex((item) => item.value === 'y');
console.log(found); // { value: 'y' }
console.log(foundIndex); // 3
Enter fullscreen mode Exit fullscreen mode

This works, but since the target is closer to the tail of the array, we can speed up the program if we use the findLast() and findLastIndex() methods to search the array from the end.

const letters = [
  { value: 'v' },
  { value: 'w' },
  { value: 'x' },
  { value: 'y' },
  { value: 'z' },
];
const found = letters.findLast((item) => item.value === 'y');
const foundIndex = letters.findLastIndex((item) => item.value === 'y');
console.log(found); // { value: 'y' }
console.log(foundIndex); // 3
Enter fullscreen mode Exit fullscreen mode

Another use case may require that we specifically search for an array element from the end to get the right result. For example, if we want to find the last even number in a list of numbers find() and get findIndex() an incorrect result:

const nums = [7, 14, 3, 8, 10, 9];
// дает 14 вместо 10 
const lastEven = nums.find((value) => value % 2 === 0);
// дает 1 вместо 4 
const lastEvenIndex = nums.findIndex((value) => value % 2 === 0);
console.log(lastEven); // 14 
console.log(lastEvenIndex); // 1
Enter fullscreen mode Exit fullscreen mode

Output
So, we have seen the new features that ES13 brings to JavaScript. Use them to increase your productivity as a developer and write nice and clean code.

Оцените статью
devanswers.ru
Добавить комментарий