11 удивительных функций Javascript в ES13
Danil Kambulov
Posted on August 29, 2022
Всем привет. На сегодняшний день 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
ES13 убирает это ограничение. Теперь мы можем инициализировать поля без метода, следующим образом:
class Person {
name = 'Alex';
age = 22;
}
const person = new Person();
console.log(person.name); // Alex
console.log(person.age); // 22
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
С 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);
Обратите внимание, что ошибка, вызванная здесь, является синтаксической ошибкой, которая возникает во время компиляции, поэтому код ниже не выполняется. Компилятор не ожидает, что вы попытаетесь получить доступ к приватным полям извне класса, поэтому он предполагает, что вы пытаетесь объявить один из них.
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)
С ES13 теперь мы можем сделать так:
function setTimeoutAsync(timeout) {
return new Promise((resolve) => {
setTimeout(() => {
resolve();
}, timeout);
});
}
// Ожидает таймаут и ошибка не вызывается
await setTimeoutAsync(3000);
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
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' ]
6. Проверка на наличие приватных элементов
Используя оператор in
мы можем использовать эту новую функцию, чтобы проверить, есть ли в объекте приватное поле.
Он обеспечивает компактный способ проверки наличия у объекта приватного поля.
class Car {
#color;
hasColor() {
return #color in this;
}
}
const car = new Car();
console.log(car.hasColor()); // true;
Оператор 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
7. Метод at()
Обычно мы используем квадратные скобки ([])
в JavaScript для доступа к N
-му элементу массива.
const arr = ['a', 'b', 'c', 'd'];
console.log(arr[1]); // b
Для доступа к 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
Новый метод at()
позволяет нам сделать это более кратко и лаконично. Чтобы получить доступ к N
-му элементу из конца массива, мы просто передаем отрицательное значение -N
в at()
.
const arr = ['a', 'b', 'c', 'd'];
// 1ый элемент с конца
console.log(arr.at(-1)); // d
// 2ой элемент с конца
console.log(arr.at(-2)); // c
Помимо массива, этим свойством обладают 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
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);
Теперь мы можем указать флаг 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);
С установленным 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
Но есть определенные проблемы с этим подходом. Во-первых, 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
Другая проблема заключается в том, что для объектов, созданных с использованием 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'));
Один из способов решения этих проблем — использовать вызов 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
Это не очень удобно. Мы можем написать повторно используемую функцию, чтобы не повторяться:
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
Однако в этом нет необходимости, так как мы можем использовать новый встроенный 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
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}`);
}
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
Это работает, но поскольку целевой объект находится ближе к хвосту массива, мы можем ускорить работу этой программы, если воспользуемся методами 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
Другой вариант использования может потребовать, чтобы мы специально искали элемент массива с конца, чтобы получить правильный результат. Например, если мы хотим найти последнее четное число в списке чисел 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
Вывод
Итак, мы увидели новые функции, которые ES13 привносит в JavaScript. Используйте их, чтобы повысить свою продуктивность как разработчика и писать красивый и чистый код.
Posted on August 29, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.