11 удивительных функций Javascript в ES13

kambulov

Danil Kambulov

Posted on August 29, 2022

11 удивительных функций Javascript в ES13

Всем привет. На сегодняшний день JavaScript, как и многие другие языки программирования постоянно развивается. Каждый год в язык добавляются новые возможности, которые позволяют разработчикам писать более выразительный и лаконичный код.

Давайте рассмотрим последние нововведения, добавленные в ECMAScript 2022 (ES13), и посмотрим примеры их реализации, чтобы лучше понять их.

1. Class Field Declaration

До ES13 поля класса могли быть инициализированы только в специальном методе constructor(). В отличие от других языков, мы не можем объявить или определить их во внешней области класса.

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 убирает это ограничение. Теперь мы можем инициализировать поля без метода, следующим образом:

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. Приватные методы и поля
Во многих других языках, также существуют «защищённые» поля, доступные только внутри класса или для дочерних классов.

Ранее было невозможно объявить приватные поля в классе, потому что они не реализованы в JavaScript на уровне языка, но на практике они очень удобны, поэтому их эмулируют. Чаще всего приватные поля имеют префикс подчеркивания в начале имени. Тем самым указывая на то, что он закрыт, но к нему по-прежнему можно было получить доступ и изменить его извне класса.

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

С ES13 мы теперь можем добавлять приватные поля в класс, добавив к нем хэштег (#). Теперь любая попытка получить к ним доступ извне класса приведет к ошибке:

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

Обратите внимание, что ошибка, вызванная здесь, является синтаксической ошибкой, которая возникает во время компиляции, поэтому код ниже не выполняется. Компилятор не ожидает, что вы попытаетесь получить доступ к приватным полям извне класса, поэтому он предполагает, что вы пытаетесь объявить один из них.

3. Await на верхнем уровне
В JavaScript оператор await используется для приостановки выполнения асинхронной функции и ожидает до тех пор, пока обещание не будет выполнено или отклонено.

Ранее мы могли использовать этот оператор только в асинхронной функции - функции, объявленной с ключевым словом async. Мы не могли бы сделать это в глобальном масштабе.

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

С ES13 теперь мы можем сделать так:

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

4. Статические (приватные) свойства и методы класса
Теперь мы можем объявлять статические поля и статические приватные методы для класса в ES13. Статические методы могут обращаться к другим приватным/публичным статическим элементам класса с помощью ключевого слова this, а методы экземпляра могут обращаться к ним с помощью 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. Статический блок класса
ES13 позволяет определять static блоки, которые будут выполняться только один раз, при создании класса. Это похоже на статические конструкторы в других языках с поддержкой объектно-ориентированного программирования, таких как C# или Java.

Класс может иметь любое количество статических блоков static {} в теле своего класса. Они будут выполняться вместе с любыми чередующимися инициализаторами статических полей в том порядке, в котором они объявлены. Мы также можем использовать свойство super в static блоке для доступа к свойствам родительского класса.

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. Проверка на наличие приватных элементов
Используя оператор in мы можем использовать эту новую функцию, чтобы проверить, есть ли в объекте приватное поле.
Он обеспечивает компактный способ проверки наличия у объекта приватного поля.

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

Оператор in может различать приватные поля с одинаковыми именами от разных классов:

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. Метод at()
Обычно мы используем квадратные скобки ([]) в JavaScript для доступа к N-му элементу массива.

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

Для доступа к N-му элементу из конца массива в квадратных скобках мы должны использовать следующий индекс 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

Новый метод at() позволяет нам сделать это более кратко и лаконично. Чтобы получить доступ к N-му элементу из конца массива, мы просто передаем отрицательное значение -N в 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 и TypedArray.

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 индексы совпадения
Эта новая функция позволяет нам указать, что мы хотим получить как начальный, так и конечный индексы совпадений RegExp объекта в данной строке.

Раньше мы могли получить только начальный индекс совпадения регулярного выражения в строке.

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

Теперь мы можем указать флаг d для регулярного выражения, чтобы получить два индекса, где совпадение начинается и заканчивается.

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

С установленным d флагом возвращаемый объект будет иметь свойство indices, содержащее начальный и конечный индексы.

9. Метод Object.hasOwn()
В JavaScript мы можем использовать Object.prototype.hasOwnProperty() метод для проверки наличия у объекта заданного свойства.

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

Но есть определенные проблемы с этим подходом. Во-первых, Object.prototype.hasOwnProperty() метод не защищен — его можно переопределить, определив пользовательский hasOwnProperty() метод для класса, который может вести себя совершенно иначе, чем 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

Другая проблема заключается в том, что для объектов, созданных с использованием null прототипа (с использованием Object.create(null)), попытка вызвать для них этот метод вызовет ошибку.

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

Один из способов решения этих проблем — использовать вызов call() метода для Object.prototype.hasOwnProperty Functionсвойства, например:

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

Это не очень удобно. Мы можем написать повторно используемую функцию, чтобы не повторяться:

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

Однако в этом нет необходимости, так как мы можем использовать новый встроенный Object.hasOwn() метод. Как и наша повторно используемая функция, она принимает объект и свойство в качестве аргументов и возвращает true значение, если указанное свойство является прямым свойством объекта. В противном случае возвращается 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
Объекты ошибок теперь имеют cause свойство для указания исходной ошибки, вызвавшей возникновение ошибки. Это помогает добавить к ошибке дополнительную информацию и помогает диагностировать непредвиденное поведение. Мы можем указать причину ошибки, установив cause свойство объекта, переданного Error() конструктору в качестве второго аргумента.

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. Поиск в массиве
В JavaScript мы можем использовать Array.find() метод для поиска элемента в массиве, который проходит заданное условие проверки. Точно так же мы можем использовать findIndex(), чтобы найти индекс такого элемента. В то время как find() и findIndex() оба начинают поиск с первого элемента массива, есть случаи, когда было бы предпочтительнее начать поиск с последнего элемента.

Есть сценарии, в которых мы знаем, что поиск по последнему элементу может повысить производительность. Например, здесь мы пытаемся получить элемент в массиве с value='y'. С find() и 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

Это работает, но поскольку целевой объект находится ближе к хвосту массива, мы можем ускорить работу этой программы, если воспользуемся методами findLast() и findLastIndex() для поиска в массиве с конца.

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

Другой вариант использования может потребовать, чтобы мы специально искали элемент массива с конца, чтобы получить правильный результат. Например, если мы хотим найти последнее четное число в списке чисел find() и получим findIndex() неправильный результат:

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

Вывод
Итак, мы увидели новые функции, которые ES13 привносит в JavaScript. Используйте их, чтобы повысить свою продуктивность как разработчика и писать красивый и чистый код.

💖 💪 🙅 🚩
kambulov
Danil Kambulov

Posted on August 29, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related