Hello, world!
Представляю вашему вниманию перевод первой части этой замечательной статьи, посвященной возможностям JS и TS последних трех лет, которые вы могли пропустить.
В первой части мы поговорим о возможностях JS, во второй — о возможностях TS.
Это первая часть.
Обратите внимание: название почти каждой возможности — это также ссылка на соответствующий раздел MDN.
Теггированые шаблонные литералы / Tagged template literals: если после названия функции указать шаблонный литерал, то функция получит части шаблонных литералов и значения шаблона, например:
// Предположим, что мы хотим форматировать число, содержащееся в строке
function formatNumbers(strings: TemplateStringsArray, number: number): string {
return strings[0] + number.toFixed(2) + strings[1];
}
console.log(formatNumbers`This is the value: ${0}, it's important.`);
// This is the value: 0.00, it's important.
// Или мы хотим "переводить" (в данном случае в нижний регистр) ключи переводов, содержащиеся в строке
function translateKey(key: string): string {
return key.toLocaleLowerCase();
}
function translate(strings: TemplateStringsArray, ...expressions: string[]): string {
return strings.reduce((accumulator, currentValue, index) => accumulator + currentValue + translateKey(expressions[index] ?? ''), '');
}
console.log(translate`Hello, this is ${'NAME'} to say ${'MESSAGE'}.`);
// Hello, this is name to say message.
__Символы / Symbols__: примитивы, представляющие собой гарантировано уникальные значения (Symbol("foo") === Symbol("foo"); // false), которые часто используются в качестве ключей объектов во избежание коллизий с другими ключами, например:
const obj: { [index: string]: string } = {};
const symbolA = Symbol('a');
const symbolB = Symbol.for('b');
console.log(symbolA.description); // "a"
obj[symbolA] = 'a';
obj[symbolB] = 'b';
obj['c'] = 'c';
obj.d = 'd';
console.log(obj[symbolA]); // "a"
console.log(obj[symbolB]); // "b"
// Ключ не может быть другим символов или быть не символом
console.log(obj[Symbol('a')]); // undefined
console.log(obj['a']); // undefined
// Ключи-символы не "перечисляются" (enumerated) при использовании `for/in`.
for (const i in obj) {
console.log(i); // "c", "d"
}
__Оператор опциональной последовательности / Optional chaining (?.)__: обычно используется для безопасного доступа к свойству потенциально несуществующего/неопределенного (undefined) объекта, но также может использоваться для безопасного доступа по индексу к элементу потенциально несуществующего массива и вызова потенциально несуществующей функции, например:
// Раньше:
// Если у нас был потенциально несуществующий объект,
// мы не могли легко получить доступ к его свойству
const object: { name: string } | undefined = Math.random() > 0.5 ? undefined : { name: 'test' };
const value = object.name; // TypeError: 'object' is possibly 'undefined'
// Мы должны были проверять "определенность" объекта
// Это ухудшало читаемость кода и становилось сложным в случае вложенных объектов
const objectOld: { name: string } | undefined = Math.random() > 0.5 ? undefined : { name: 'test' };
const valueOld = objectOld ? objectOld.name : undefined;
// Сейчас:
// Мы можем использовать оператор опциональной последовательности
// для безопасного доступа к свойству потенциально несуществующего объекта
const objectNew: { name: string } | undefined = Math.random() > 0.5 ? undefined : { name: 'test' };
const valueNew = objectNew?.name;
// Его также можно использовать для безопасного доступа по индексу и вызова функции
const array: string[] | undefined = Math.random() > 0.5 ? undefined : ['test'];
const item = array?.[0];
const func: (() => string) | undefined = Math.random() > 0.5 ? undefined : () => 'test';
const result = func?.();
__Оператор нулевого слияния / Nullish coalescing operator (??)__: является альтернативой оператора ||. Отличие между этими операторами состоит в том, что || применяется ко всем ложным значениям, а ?? — только к undefined и null, например:
const value: string | undefined = Math.random() > 0.5 ? undefined : 'test';
// Раньше:
// Для условного присвоения значения переменной мы использовали оператор `||`
const anotherValue = value || 'hello';
console.log(anotherValue); // "test" или "hello"
// Это не всегда работало хорошо
const incorrectValue = '' || 'incorrect';
console.log(incorrectValue); // всегда "incorrect"
const anotherIncorrectValue = 0 || 'incorrect';
console.log(anotherIncorrectValue); // всегда "incorrect"
// Сейчас:
// Оператор нулевого слияния применяется только в отношении `undefined` и `null`
const newValue = value ?? 'hello';
console.log(newValue) // "test" или "hello"
// Ложные значения не заменяются
const correctValue = '' ?? 'incorrect';
console.log(correctValue); // всегда ""
const anotherCorrectValue = 0 ?? 'incorrect';
console.log(anotherCorrectValue); // всегда 0
import(): функциональное выражение динамического импорта — как import ... from '...', но во время выполнения кода и с возможностью использования переменных:
let importModule;
if (shouldImport) {
importModule = await import('./module.mjs');
}
__String.matchAll()__: возвращает несколько совпадений регулярного выражения, включая группы захвата (capture groups), без использования циклов:
const stringVar = 'testhello,testagain,';
// Раньше:
// Получаем совпадения, но без групп захвата
console.log(stringVar.match(/test([\w]+?),/g));
// ["testhello,", "testagain,"]
// Получаем одно совпадение с группой захвата
const singleMatch = stringVar.match(/test([\w]+?),/);
if (singleMatch) {
console.log(singleMatch[0]); // "testhello,"
console.log(singleMatch[1]); // "hello"
}
// Получаем все совпадения с группами захвата (метод `exec` запоминает индекс последнего совпадения)
// `execMatch` должен быть определен за пределами цикла (для сохранения состояния) и быть глобальным (флаг `g`),
// иначе цикл будет бесконечным
const regex = /test([\w]+?),/g;
let execMatch;
while ((execMatch = regex.exec(stringVar)) !== null) {
console.log(execMatch[0]); // "testhello,", "testagain,"
console.log(execMatch[1]); // "hello", "again"
}
// Сейчас:
// Регулярное выражение должно быть глобальным
const matchesIterator = stringVar.matchAll(/test([\w]+?),/g);
// Итерация или преобразование в массив (Array.from()), доступ по индексу запрещен
for (const match of matchesIterator) {
console.log(match[0]); // "testhello,", "testagain,"
console.log(match[1]); // "hello", "again"
}
__Promise.allSettled()__: похож на Promise.all(), но ожидает (любого) разрешения всех промисов, а не возвращает первую ошибку, что облегчает обработку ошибок:
async function success1() { return 'a' };
async function success2() { return 'b' };
async function fail1() { throw 'fail 1' };
async function fail2() { throw 'fail 2' };
// Раньше:
console.log(await Promise.all([success1(), success2()])); // ["a", "b"]
// но:
try {
await Promise.all([success1(), success2(), fail1(), fail2()]);
} catch (e) {
console.log(e); // "fail 1"
}
// Мы перехватываем одну ошибку и не имеем доступа к "успешным" значениям
// Фикс (плохой код):
console.log(await Promise.all([ // ["a", "b", undefined, undefined]
success1().catch(e => { console.log(e); }),
success2().catch(e => { console.log(e); }),
fail1().catch(e => { console.log(e); }), // "fail 1"
fail2().catch(e => { console.log(e); })])); // "fail 2"
// Сейчас:
const results = await Promise.allSettled([success1(), success2(), fail1(), fail2()]);
const successfulResults = results
.filter(result => result.status === 'fulfilled')
.map(result => (result as PromiseFulfilledResult<string>).value);
console.log(successfulResults); // ["a", "b"]
results.filter(result => result.status === 'rejected').forEach(error => {
console.log((error as PromiseRejectedResult).reason); // "fail 1", "fail 2"
});
// или:
for (const result of results) {
if (result.status === 'fulfilled') {
console.log(result.value); // "a", "b"
} else if (result.status === 'rejected') {
console.log(result.reason); // "fail 1", "fail 2"
}
}
__BigInt__: тип данных, позволяющий хранить (с сохранением точности) и оперировать большими (целыми) числами. Для создания значения такого типа используется либо конструктор BigInt, либо символ n в конце числа:
// Раньше:
// JS хранит числа как числа с плавающей запятой, что всегда влечет небольшую потерю точности,
// которая существенно возрастает после определенного числа
const maxSafeInteger = 9007199254740991;
console.log(maxSafeInteger === Number.MAX_SAFE_INTEGER); // true
// БОльшие числа сравниваются некорректно
console.log(Number.MAX_SAFE_INTEGER + 1 === Number.MAX_SAFE_INTEGER + 2); // true
// Сейчас:
// Тип данных `BigInt` теоретически позволяет хранить и оперировать неопределенно большими (целыми) числами
const maxSafeIntegerPreviously = 9007199254740991n;
console.log(maxSafeIntegerPreviously); // 9007199254740991
const anotherWay = BigInt(9007199254740991);
console.log(anotherWay); // 9007199254740991
// Обратите внимание: в конструктор нельзя передавать числа, которые больше чем MAX_SAFE_INTEGER
const incorrect = BigInt(9007199254740992);
console.log(incorrect); // 9007199254740992
const incorrectAgain = BigInt(9007199254740993);
console.log(incorrectAgain); // 9007199254740992
// Но можно передавать строки или использовать другой синтаксис
const correct = BigInt('9007199254740993');
console.log(correct); // 9007199254740993
const correctAgain = 9007199254740993n;
console.log(correctAgain); // 9007199254740993
// Другие форматы также могут передаваться в виде строк
const hex = BigInt('0x1fffffffffffff');
console.log(hex); // 9007199254740991
const octal = BigInt('0o377777777777777777');
console.log(octal); // 9007199254740991
const binary = BigInt('0b11111111111111111111111111111111111111111111111111111');
console.log(binary); // 9007199254740991
// Большинство арифметических операций работает, как ожидается,
// если другой операнд также является `BigInt`
// Все операции возвращают `BigInt`
const addition = maxSafeIntegerPreviously + 2n;
console.log(addition); // 9007199254740993
const multiplication = maxSafeIntegerPreviously * 2n;
console.log(multiplication); // 18014398509481982
const subtraction = multiplication - 10n;
console.log(subtraction); // 18014398509481972
const modulo = multiplication % 10n;
console.log(modulo); // 2
const exponentiation = 2n ** 54n;
console.log(exponentiation); // 18014398509481984
const exponentiationAgain = 2n^54n;
console.log(exponentiationAgain); // 18014398509481984
const negative = exponentiation * -1n;
console.log(negative); // -18014398509481984
// Деление работает немного иначе, поскольку `BigInt` может хранить только целые числа
const division = multiplication / 2n;
console.log(division); // 9007199254740991
// Для целых чисел, которые делятся без остатка, это работает хорошо
// Иначе результат округляется до целого числа в меньшую сторону
const divisionAgain = 5n / 2n;
console.log(divisionAgain); // 2
// Проверка на равенство с обычными числами является нестрогой
console.log(0n === 0); // false
console.log(0n == 0); // true
// Сравнение работает как ожидается
console.log(1n < 2); // true
console.log(2n > 1); // true
console.log(2 > 2); // false
console.log(2n > 2); // false
console.log(2n >= 2); // true
// Тип
console.log(typeof 1n); // "bigint"
__globalThis__: предоставляет доступ к глобальным переменным, независимо от среды выполнения кода (браузер, Node.js и др.):
console.log(globalThis.Math); // объект `Math`
import.meta: в числе прочего, при использовании модулей ES, предоставляет доступ к URL текущего модуля:
console.log(import.meta.url); // "file://..."
export * as… from '...': позволяет с легкостью повторно экспортировать (re-export) дефолтные экспорты в качестве субмодулей:
export * as am from 'another-module'
import { am } from 'module'
__String.replaceAll()__: заменяет все вхождения подстроки в строке, является альтернативой регулярного выражения с флагом g:
const testString = 'hello/greetings everyone/everybody';
// Раньше:
// Заменяет только первое вхождение
console.log(testString.replace('/', '|'));
// 'hello|greetings everyone/everybody'
// Заменяет все вхождения
// Регулярное выражение + экранирование + глобальный флаг
console.log(testString.replace(/\//g, '|'));
// 'hello|greetings everyone|everybody'
// Сейчас:
// Заменяет все вхождения
// Чище и быстрее
console.log(testString.replaceAll('/', '|'));
// 'hello|greetings everyone|everybody'
__Promise.any()__: возвращается первое "успешное" значение. Отклоняется только при отклонении всех промисов (в этом случае возвращается AggregateError), в отличие от Promise.race(), который отклоняется при отклонении любого промиса:
async function success1() { return 'a' };
async function success2() { return 'b' };
async function fail1() { throw 'fail 1' };
async function fail2() { throw 'fail 2' };
// Раньше:
console.log(await Promise.race([success1(), success2()])); // "a"
// но:
try {
await Promise.race([fail1(), fail2(), success1(), success2()]);
} catch (e) {
console.log(e); // "fail 1"
}
// Перехватываем одну ошибку и не имеем доступа к "успешным" значениям
// Фикс (плохой код):
console.log(await Promise.race([ // "a"
fail1().catch(e => { console.log(e); }), // "fail 1"
fail2().catch(e => { console.log(e); }), // "fail 2"
success1().catch(e => { console.log(e); }),
success2().catch(e => { console.log(e); })]));
// Сейчас:
console.log(await Promise.any([fail1(), fail2(), success1(), success2()])); // "a"
try {
await Promise.any([fail1(), fail2()]);
} catch (e) {
console.log(e); // [AggregateError]
console.log(e.errors); // ["fail 1", "fail 2"]
}
Оператор присваивания нулевого слияния / Nullish coalescing assignment (??=): присваивает новое значение переменной только в том случае, когда текущим значением переменной является null или undefined:
let x1 = undefined;
let x2 = 'a';
const getNewValue = () => 'b';
x1 ??= 'b';
console.log(x1) // "b"
// Обратите внимание: `getNewValue()` не выполняется
x2 ??= getNewValue();
console.log(x1) // "a"
Оператор присваивания логического И / Logical and assignment (&&=): присваивает новое значение переменной только в том случае, когда текущим значением переменной является истинное значение:
let x1 = undefined;
let x2 = 'a';
const getNewValue = () => 'b';
x1 &&= getNewValue();
console.log(x1) // undefined
x2 &&= 'b';
console.log(x1) // "b"
Оператор присваивания логического ИЛИ / Logical or assignment (||=): присваивает новое значение переменной только в том случае, когда текущим значением переменной является ложное значение:
let x1 = undefined;
let x2 = 'a';
const getNewValue = () => 'b';
x1 ||= 'b';
console.log(x1) // "b"
x2 ||= getNewValue();
console.log(x1) // "a"
__WeakRef__: содержит "слабую" ссылку на объект. Слабая ссылка не препятствует уничтожению объекта сборщиком мусора:
const ref = new WeakRef(element);
// Получаем значение, если объект/элемент существует и не был уничтожен сборщиком мусора
const value = ref.deref;
console.log(value); // undefined
// Похоже, объекта больше нет
_Разделители числовых литералов / Numeric literal separators (``)__: позволяет разделять числа для повышения читаемости, не влияет на функционал:
const int = 1_000_000_000;
const float = 1_000_000_000.999_999_999;
const max = 9_223_372_036_854_775_807n;
const binary = 0b1011_0101_0101;
const octal = 0o1234_5670;
const hex = 0xD0_E0_F0;
await верхнего уровня / Top level await: позволяет использовать ключевое слово await на верхнем уровне модулей, что избавляет от необходимости оборачивать асинхронный код в асинхронную функцию и улучшает обработку ошибок:
async function asyncFuncSuccess() {
return 'test';
}
async function asyncFuncFail() {
throw new Error('Test');
}
// Раньше:
// Ждать разрешения промиса можно было только внутри асинхронной функции
// await asyncFuncSuccess(); // SyntaxError: await is only valid in async functions
// Обертка приводит к усложнению обработки ошибок и потере контроля за порядком выполнения кода
try {
(async () => {
console.log(await asyncFuncSuccess()); // "test"
try {
await asyncFuncFail();
} catch (e) {
// Иначе ошибки не будут перехвачены (или будут перехвачены слишком поздно с усложненной трассировкой стека)
console.error(e); // Error: "Test"
throw e;
}
})();
} catch (e) {
// Не выполняется или выполняется слишком поздно
console.error(e);
}
// Выводится до разрешения промиса
console.log('Hey'); // "Hey"
// Сейчас:
// Файл должен быть модулем (`"type"" "module"` в `package.json` или расширение ".mjs")
console.log(await asyncFuncSuccess()); // "test"
try {
await asyncFuncFail();
} catch (e) {
console.error(e); // Error: "Test"
}
// Выводится после разрешения промиса
console.log('Hello'); // "Hello"
#private: делает членов класса (свойства и методы) приватными (закрытыми). Такие члены доступны только внутри класса, в котором они определены. Они не могут удаляться или определяться динамически. Любое некорректное поведение завершается синтаксической ошибкой JS. В TS-проектах для обозначения приватных членов класса используется ключевое слово private.
class ClassWithPrivateField {
#privateField;
#anotherPrivateField = 4;
constructor() {
this.#privateField = 42; // Ok
this.#privateField; // SyntaxError
this.#undeclaredField = 444; // SyntaxError
console.log(this.#anotherPrivateField); // 4
}
}
const instance = new ClassWithPrivateField();
instance.#privateField === 42; // SyntaxError
Статические члены класса / Static class members: делает поле класса (свойство или метод) статическим:
class Logger {
static id = 'Logger1';
static type = 'GenericLogger';
static log(message: string | Error) {
console.log(message);
}
}
class ErrorLogger extends Logger {
static type = 'ErrorLogger';
static qualifiedType;
static log(e: Error) {
return super.log(e.toString());
}
}
console.log(Logger.type); // "GenericLogger"
Logger.log('Test'); // "Test"
// Инстанцирование класса, содержащего только статические поля, бесполезно и
// выполняется здесь только в целях демонстрации
const log = new Logger();
ErrorLogger.log(new Error('Test')); // Error: "Test" (инстанцирование суперкласса не меняет поведение подклассов)
console.log(ErrorLogger.type); // "ErrorLogger"
console.log(ErrorLogger.qualifiedType); // undefined
console.log(ErrorLogger.id); // "Logger1"
// Выбрасывается исключение, поскольку `log` - статический метод, а не метод экземпляра
console.log(log.log()); // log.log is not a function
Статические блоки инициализации / Static initialization blocks: блок кода, который выполняется при инициализации класса. Как правило, такие блоки используются в качестве "конструкторов" статических членов классов:
class Test {
static staticProperty1 = 'Property 1';
static staticProperty2;
static {
this.staticProperty2 = 'Property 2';
}
}
console.log(Test.staticProperty1); // "Property 1"
console.log(Test.staticProperty2); // "Property 2"
Утверждение импорта / Import assertion (пока доступно только в V8): определяет тип импортируемого ресурса. Может использоваться, например, для импорта JSON без необходимости его разбора:
import json from './foo.json' assert { type: 'json' };
console.log(json.answer); // 42
Индексы совпадений регулярного выражения / RegExp match indices: начальный и конечный индексы совпадения регулярного выражения с группами захвата. Это работает с RegExp.exec(), RegExp.match() и String.matchAll():
const matchObj = /(test+)(hello+)/d.exec('start-testesthello-stop');
// Раньше:
console.log(matchObj?.index); // 9 - только начальный индекс совпадения
// Сейчас:
if (matchObj) {
// Начальный и конечный индексы совпадения
console.log(matchObj.indices[0]); // [9, 18]
// Начальный и конечный индексы групп захвата
console.log(matchObj.indices[1]); // [9, 13]
console.log(matchObj.indices[2]); // [13, 18]
}
__Негативная индексация / Negative indexing__: метод Array.at возвращает элементы массива с конца (с помощью отрицательных индексов). at(-1) является эквивалентом arr[arr.length - 1] для получения последнего элемента, но не для его установки:
console.log([4, 5].at(-1)) // 5
const array = [4, 5];
array.at(-1) = 3; // SyntaxError
__Object.hasOwn()__: альтернатива метода Object.hasOwnProperty(), позволяющая определять наличие в объекте указанного свойства. Работает лучше в некоторых крайних случаях:
const obj = { name: 'test' };
console.log(Object.hasOwn(obj, 'name')); // true
console.log(Object.hasOwn(obj, 'gender')); // false
__Причина ошибки / Error cause__: при повторном выбросе исключения (re-throwing) в качестве второго аргумента в конструктор Error можно передать объект со свойством cause, значением которого является оригинальное исключение:
try {
try {
connectToDatabase();
} catch (err) {
throw new Error('Не удалось подключиться к базе данных.', { cause: err });
}
} catch (err) {
console.log(err.cause); // ReferenceError: connectToDatabase is not defined
}
На этом перевод первой части, посвященной возможностям JS, завершен. В следующей части мы поговорим о возможностях TS.
Надеюсь, вы узнали что-то новое и не зря потратили время.
Happy coding!
Представляю вашему вниманию перевод первой части этой замечательной статьи, посвященной возможностям JS и TS последних трех лет, которые вы могли пропустить.
В первой части мы поговорим о возможностях JS, во второй — о возможностях TS.
Это первая часть.
Обратите внимание: название почти каждой возможности — это также ссылка на соответствующий раздел MDN.
ECMAScript
До ES2020 (возможности, о которых многие не знают)
Теггированые шаблонные литералы / Tagged template literals: если после названия функции указать шаблонный литерал, то функция получит части шаблонных литералов и значения шаблона, например:
// Предположим, что мы хотим форматировать число, содержащееся в строке
function formatNumbers(strings: TemplateStringsArray, number: number): string {
return strings[0] + number.toFixed(2) + strings[1];
}
console.log(formatNumbers`This is the value: ${0}, it's important.`);
// This is the value: 0.00, it's important.
// Или мы хотим "переводить" (в данном случае в нижний регистр) ключи переводов, содержащиеся в строке
function translateKey(key: string): string {
return key.toLocaleLowerCase();
}
function translate(strings: TemplateStringsArray, ...expressions: string[]): string {
return strings.reduce((accumulator, currentValue, index) => accumulator + currentValue + translateKey(expressions[index] ?? ''), '');
}
console.log(translate`Hello, this is ${'NAME'} to say ${'MESSAGE'}.`);
// Hello, this is name to say message.
__Символы / Symbols__: примитивы, представляющие собой гарантировано уникальные значения (Symbol("foo") === Symbol("foo"); // false), которые часто используются в качестве ключей объектов во избежание коллизий с другими ключами, например:
const obj: { [index: string]: string } = {};
const symbolA = Symbol('a');
const symbolB = Symbol.for('b');
console.log(symbolA.description); // "a"
obj[symbolA] = 'a';
obj[symbolB] = 'b';
obj['c'] = 'c';
obj.d = 'd';
console.log(obj[symbolA]); // "a"
console.log(obj[symbolB]); // "b"
// Ключ не может быть другим символов или быть не символом
console.log(obj[Symbol('a')]); // undefined
console.log(obj['a']); // undefined
// Ключи-символы не "перечисляются" (enumerated) при использовании `for/in`.
for (const i in obj) {
console.log(i); // "c", "d"
}
ES2020
__Оператор опциональной последовательности / Optional chaining (?.)__: обычно используется для безопасного доступа к свойству потенциально несуществующего/неопределенного (undefined) объекта, но также может использоваться для безопасного доступа по индексу к элементу потенциально несуществующего массива и вызова потенциально несуществующей функции, например:
// Раньше:
// Если у нас был потенциально несуществующий объект,
// мы не могли легко получить доступ к его свойству
const object: { name: string } | undefined = Math.random() > 0.5 ? undefined : { name: 'test' };
const value = object.name; // TypeError: 'object' is possibly 'undefined'
// Мы должны были проверять "определенность" объекта
// Это ухудшало читаемость кода и становилось сложным в случае вложенных объектов
const objectOld: { name: string } | undefined = Math.random() > 0.5 ? undefined : { name: 'test' };
const valueOld = objectOld ? objectOld.name : undefined;
// Сейчас:
// Мы можем использовать оператор опциональной последовательности
// для безопасного доступа к свойству потенциально несуществующего объекта
const objectNew: { name: string } | undefined = Math.random() > 0.5 ? undefined : { name: 'test' };
const valueNew = objectNew?.name;
// Его также можно использовать для безопасного доступа по индексу и вызова функции
const array: string[] | undefined = Math.random() > 0.5 ? undefined : ['test'];
const item = array?.[0];
const func: (() => string) | undefined = Math.random() > 0.5 ? undefined : () => 'test';
const result = func?.();
__Оператор нулевого слияния / Nullish coalescing operator (??)__: является альтернативой оператора ||. Отличие между этими операторами состоит в том, что || применяется ко всем ложным значениям, а ?? — только к undefined и null, например:
const value: string | undefined = Math.random() > 0.5 ? undefined : 'test';
// Раньше:
// Для условного присвоения значения переменной мы использовали оператор `||`
const anotherValue = value || 'hello';
console.log(anotherValue); // "test" или "hello"
// Это не всегда работало хорошо
const incorrectValue = '' || 'incorrect';
console.log(incorrectValue); // всегда "incorrect"
const anotherIncorrectValue = 0 || 'incorrect';
console.log(anotherIncorrectValue); // всегда "incorrect"
// Сейчас:
// Оператор нулевого слияния применяется только в отношении `undefined` и `null`
const newValue = value ?? 'hello';
console.log(newValue) // "test" или "hello"
// Ложные значения не заменяются
const correctValue = '' ?? 'incorrect';
console.log(correctValue); // всегда ""
const anotherCorrectValue = 0 ?? 'incorrect';
console.log(anotherCorrectValue); // всегда 0
import(): функциональное выражение динамического импорта — как import ... from '...', но во время выполнения кода и с возможностью использования переменных:
let importModule;
if (shouldImport) {
importModule = await import('./module.mjs');
}
__String.matchAll()__: возвращает несколько совпадений регулярного выражения, включая группы захвата (capture groups), без использования циклов:
const stringVar = 'testhello,testagain,';
// Раньше:
// Получаем совпадения, но без групп захвата
console.log(stringVar.match(/test([\w]+?),/g));
// ["testhello,", "testagain,"]
// Получаем одно совпадение с группой захвата
const singleMatch = stringVar.match(/test([\w]+?),/);
if (singleMatch) {
console.log(singleMatch[0]); // "testhello,"
console.log(singleMatch[1]); // "hello"
}
// Получаем все совпадения с группами захвата (метод `exec` запоминает индекс последнего совпадения)
// `execMatch` должен быть определен за пределами цикла (для сохранения состояния) и быть глобальным (флаг `g`),
// иначе цикл будет бесконечным
const regex = /test([\w]+?),/g;
let execMatch;
while ((execMatch = regex.exec(stringVar)) !== null) {
console.log(execMatch[0]); // "testhello,", "testagain,"
console.log(execMatch[1]); // "hello", "again"
}
// Сейчас:
// Регулярное выражение должно быть глобальным
const matchesIterator = stringVar.matchAll(/test([\w]+?),/g);
// Итерация или преобразование в массив (Array.from()), доступ по индексу запрещен
for (const match of matchesIterator) {
console.log(match[0]); // "testhello,", "testagain,"
console.log(match[1]); // "hello", "again"
}
__Promise.allSettled()__: похож на Promise.all(), но ожидает (любого) разрешения всех промисов, а не возвращает первую ошибку, что облегчает обработку ошибок:
async function success1() { return 'a' };
async function success2() { return 'b' };
async function fail1() { throw 'fail 1' };
async function fail2() { throw 'fail 2' };
// Раньше:
console.log(await Promise.all([success1(), success2()])); // ["a", "b"]
// но:
try {
await Promise.all([success1(), success2(), fail1(), fail2()]);
} catch (e) {
console.log(e); // "fail 1"
}
// Мы перехватываем одну ошибку и не имеем доступа к "успешным" значениям
// Фикс (плохой код):
console.log(await Promise.all([ // ["a", "b", undefined, undefined]
success1().catch(e => { console.log(e); }),
success2().catch(e => { console.log(e); }),
fail1().catch(e => { console.log(e); }), // "fail 1"
fail2().catch(e => { console.log(e); })])); // "fail 2"
// Сейчас:
const results = await Promise.allSettled([success1(), success2(), fail1(), fail2()]);
const successfulResults = results
.filter(result => result.status === 'fulfilled')
.map(result => (result as PromiseFulfilledResult<string>).value);
console.log(successfulResults); // ["a", "b"]
results.filter(result => result.status === 'rejected').forEach(error => {
console.log((error as PromiseRejectedResult).reason); // "fail 1", "fail 2"
});
// или:
for (const result of results) {
if (result.status === 'fulfilled') {
console.log(result.value); // "a", "b"
} else if (result.status === 'rejected') {
console.log(result.reason); // "fail 1", "fail 2"
}
}
__BigInt__: тип данных, позволяющий хранить (с сохранением точности) и оперировать большими (целыми) числами. Для создания значения такого типа используется либо конструктор BigInt, либо символ n в конце числа:
// Раньше:
// JS хранит числа как числа с плавающей запятой, что всегда влечет небольшую потерю точности,
// которая существенно возрастает после определенного числа
const maxSafeInteger = 9007199254740991;
console.log(maxSafeInteger === Number.MAX_SAFE_INTEGER); // true
// БОльшие числа сравниваются некорректно
console.log(Number.MAX_SAFE_INTEGER + 1 === Number.MAX_SAFE_INTEGER + 2); // true
// Сейчас:
// Тип данных `BigInt` теоретически позволяет хранить и оперировать неопределенно большими (целыми) числами
const maxSafeIntegerPreviously = 9007199254740991n;
console.log(maxSafeIntegerPreviously); // 9007199254740991
const anotherWay = BigInt(9007199254740991);
console.log(anotherWay); // 9007199254740991
// Обратите внимание: в конструктор нельзя передавать числа, которые больше чем MAX_SAFE_INTEGER
const incorrect = BigInt(9007199254740992);
console.log(incorrect); // 9007199254740992
const incorrectAgain = BigInt(9007199254740993);
console.log(incorrectAgain); // 9007199254740992
// Но можно передавать строки или использовать другой синтаксис
const correct = BigInt('9007199254740993');
console.log(correct); // 9007199254740993
const correctAgain = 9007199254740993n;
console.log(correctAgain); // 9007199254740993
// Другие форматы также могут передаваться в виде строк
const hex = BigInt('0x1fffffffffffff');
console.log(hex); // 9007199254740991
const octal = BigInt('0o377777777777777777');
console.log(octal); // 9007199254740991
const binary = BigInt('0b11111111111111111111111111111111111111111111111111111');
console.log(binary); // 9007199254740991
// Большинство арифметических операций работает, как ожидается,
// если другой операнд также является `BigInt`
// Все операции возвращают `BigInt`
const addition = maxSafeIntegerPreviously + 2n;
console.log(addition); // 9007199254740993
const multiplication = maxSafeIntegerPreviously * 2n;
console.log(multiplication); // 18014398509481982
const subtraction = multiplication - 10n;
console.log(subtraction); // 18014398509481972
const modulo = multiplication % 10n;
console.log(modulo); // 2
const exponentiation = 2n ** 54n;
console.log(exponentiation); // 18014398509481984
const exponentiationAgain = 2n^54n;
console.log(exponentiationAgain); // 18014398509481984
const negative = exponentiation * -1n;
console.log(negative); // -18014398509481984
// Деление работает немного иначе, поскольку `BigInt` может хранить только целые числа
const division = multiplication / 2n;
console.log(division); // 9007199254740991
// Для целых чисел, которые делятся без остатка, это работает хорошо
// Иначе результат округляется до целого числа в меньшую сторону
const divisionAgain = 5n / 2n;
console.log(divisionAgain); // 2
// Проверка на равенство с обычными числами является нестрогой
console.log(0n === 0); // false
console.log(0n == 0); // true
// Сравнение работает как ожидается
console.log(1n < 2); // true
console.log(2n > 1); // true
console.log(2 > 2); // false
console.log(2n > 2); // false
console.log(2n >= 2); // true
// Тип
console.log(typeof 1n); // "bigint"
__globalThis__: предоставляет доступ к глобальным переменным, независимо от среды выполнения кода (браузер, Node.js и др.):
console.log(globalThis.Math); // объект `Math`
import.meta: в числе прочего, при использовании модулей ES, предоставляет доступ к URL текущего модуля:
console.log(import.meta.url); // "file://..."
export * as… from '...': позволяет с легкостью повторно экспортировать (re-export) дефолтные экспорты в качестве субмодулей:
export * as am from 'another-module'
import { am } from 'module'
ES2021
__String.replaceAll()__: заменяет все вхождения подстроки в строке, является альтернативой регулярного выражения с флагом g:
const testString = 'hello/greetings everyone/everybody';
// Раньше:
// Заменяет только первое вхождение
console.log(testString.replace('/', '|'));
// 'hello|greetings everyone/everybody'
// Заменяет все вхождения
// Регулярное выражение + экранирование + глобальный флаг
console.log(testString.replace(/\//g, '|'));
// 'hello|greetings everyone|everybody'
// Сейчас:
// Заменяет все вхождения
// Чище и быстрее
console.log(testString.replaceAll('/', '|'));
// 'hello|greetings everyone|everybody'
__Promise.any()__: возвращается первое "успешное" значение. Отклоняется только при отклонении всех промисов (в этом случае возвращается AggregateError), в отличие от Promise.race(), который отклоняется при отклонении любого промиса:
async function success1() { return 'a' };
async function success2() { return 'b' };
async function fail1() { throw 'fail 1' };
async function fail2() { throw 'fail 2' };
// Раньше:
console.log(await Promise.race([success1(), success2()])); // "a"
// но:
try {
await Promise.race([fail1(), fail2(), success1(), success2()]);
} catch (e) {
console.log(e); // "fail 1"
}
// Перехватываем одну ошибку и не имеем доступа к "успешным" значениям
// Фикс (плохой код):
console.log(await Promise.race([ // "a"
fail1().catch(e => { console.log(e); }), // "fail 1"
fail2().catch(e => { console.log(e); }), // "fail 2"
success1().catch(e => { console.log(e); }),
success2().catch(e => { console.log(e); })]));
// Сейчас:
console.log(await Promise.any([fail1(), fail2(), success1(), success2()])); // "a"
try {
await Promise.any([fail1(), fail2()]);
} catch (e) {
console.log(e); // [AggregateError]
console.log(e.errors); // ["fail 1", "fail 2"]
}
Оператор присваивания нулевого слияния / Nullish coalescing assignment (??=): присваивает новое значение переменной только в том случае, когда текущим значением переменной является null или undefined:
let x1 = undefined;
let x2 = 'a';
const getNewValue = () => 'b';
x1 ??= 'b';
console.log(x1) // "b"
// Обратите внимание: `getNewValue()` не выполняется
x2 ??= getNewValue();
console.log(x1) // "a"
Оператор присваивания логического И / Logical and assignment (&&=): присваивает новое значение переменной только в том случае, когда текущим значением переменной является истинное значение:
let x1 = undefined;
let x2 = 'a';
const getNewValue = () => 'b';
x1 &&= getNewValue();
console.log(x1) // undefined
x2 &&= 'b';
console.log(x1) // "b"
Оператор присваивания логического ИЛИ / Logical or assignment (||=): присваивает новое значение переменной только в том случае, когда текущим значением переменной является ложное значение:
let x1 = undefined;
let x2 = 'a';
const getNewValue = () => 'b';
x1 ||= 'b';
console.log(x1) // "b"
x2 ||= getNewValue();
console.log(x1) // "a"
__WeakRef__: содержит "слабую" ссылку на объект. Слабая ссылка не препятствует уничтожению объекта сборщиком мусора:
const ref = new WeakRef(element);
// Получаем значение, если объект/элемент существует и не был уничтожен сборщиком мусора
const value = ref.deref;
console.log(value); // undefined
// Похоже, объекта больше нет
_Разделители числовых литералов / Numeric literal separators (``)__: позволяет разделять числа для повышения читаемости, не влияет на функционал:
const int = 1_000_000_000;
const float = 1_000_000_000.999_999_999;
const max = 9_223_372_036_854_775_807n;
const binary = 0b1011_0101_0101;
const octal = 0o1234_5670;
const hex = 0xD0_E0_F0;
ES2022
await верхнего уровня / Top level await: позволяет использовать ключевое слово await на верхнем уровне модулей, что избавляет от необходимости оборачивать асинхронный код в асинхронную функцию и улучшает обработку ошибок:
async function asyncFuncSuccess() {
return 'test';
}
async function asyncFuncFail() {
throw new Error('Test');
}
// Раньше:
// Ждать разрешения промиса можно было только внутри асинхронной функции
// await asyncFuncSuccess(); // SyntaxError: await is only valid in async functions
// Обертка приводит к усложнению обработки ошибок и потере контроля за порядком выполнения кода
try {
(async () => {
console.log(await asyncFuncSuccess()); // "test"
try {
await asyncFuncFail();
} catch (e) {
// Иначе ошибки не будут перехвачены (или будут перехвачены слишком поздно с усложненной трассировкой стека)
console.error(e); // Error: "Test"
throw e;
}
})();
} catch (e) {
// Не выполняется или выполняется слишком поздно
console.error(e);
}
// Выводится до разрешения промиса
console.log('Hey'); // "Hey"
// Сейчас:
// Файл должен быть модулем (`"type"" "module"` в `package.json` или расширение ".mjs")
console.log(await asyncFuncSuccess()); // "test"
try {
await asyncFuncFail();
} catch (e) {
console.error(e); // Error: "Test"
}
// Выводится после разрешения промиса
console.log('Hello'); // "Hello"
#private: делает членов класса (свойства и методы) приватными (закрытыми). Такие члены доступны только внутри класса, в котором они определены. Они не могут удаляться или определяться динамически. Любое некорректное поведение завершается синтаксической ошибкой JS. В TS-проектах для обозначения приватных членов класса используется ключевое слово private.
class ClassWithPrivateField {
#privateField;
#anotherPrivateField = 4;
constructor() {
this.#privateField = 42; // Ok
this.#privateField; // SyntaxError
this.#undeclaredField = 444; // SyntaxError
console.log(this.#anotherPrivateField); // 4
}
}
const instance = new ClassWithPrivateField();
instance.#privateField === 42; // SyntaxError
Статические члены класса / Static class members: делает поле класса (свойство или метод) статическим:
class Logger {
static id = 'Logger1';
static type = 'GenericLogger';
static log(message: string | Error) {
console.log(message);
}
}
class ErrorLogger extends Logger {
static type = 'ErrorLogger';
static qualifiedType;
static log(e: Error) {
return super.log(e.toString());
}
}
console.log(Logger.type); // "GenericLogger"
Logger.log('Test'); // "Test"
// Инстанцирование класса, содержащего только статические поля, бесполезно и
// выполняется здесь только в целях демонстрации
const log = new Logger();
ErrorLogger.log(new Error('Test')); // Error: "Test" (инстанцирование суперкласса не меняет поведение подклассов)
console.log(ErrorLogger.type); // "ErrorLogger"
console.log(ErrorLogger.qualifiedType); // undefined
console.log(ErrorLogger.id); // "Logger1"
// Выбрасывается исключение, поскольку `log` - статический метод, а не метод экземпляра
console.log(log.log()); // log.log is not a function
Статические блоки инициализации / Static initialization blocks: блок кода, который выполняется при инициализации класса. Как правило, такие блоки используются в качестве "конструкторов" статических членов классов:
class Test {
static staticProperty1 = 'Property 1';
static staticProperty2;
static {
this.staticProperty2 = 'Property 2';
}
}
console.log(Test.staticProperty1); // "Property 1"
console.log(Test.staticProperty2); // "Property 2"
Утверждение импорта / Import assertion (пока доступно только в V8): определяет тип импортируемого ресурса. Может использоваться, например, для импорта JSON без необходимости его разбора:
import json from './foo.json' assert { type: 'json' };
console.log(json.answer); // 42
Индексы совпадений регулярного выражения / RegExp match indices: начальный и конечный индексы совпадения регулярного выражения с группами захвата. Это работает с RegExp.exec(), RegExp.match() и String.matchAll():
const matchObj = /(test+)(hello+)/d.exec('start-testesthello-stop');
// Раньше:
console.log(matchObj?.index); // 9 - только начальный индекс совпадения
// Сейчас:
if (matchObj) {
// Начальный и конечный индексы совпадения
console.log(matchObj.indices[0]); // [9, 18]
// Начальный и конечный индексы групп захвата
console.log(matchObj.indices[1]); // [9, 13]
console.log(matchObj.indices[2]); // [13, 18]
}
__Негативная индексация / Negative indexing__: метод Array.at возвращает элементы массива с конца (с помощью отрицательных индексов). at(-1) является эквивалентом arr[arr.length - 1] для получения последнего элемента, но не для его установки:
console.log([4, 5].at(-1)) // 5
const array = [4, 5];
array.at(-1) = 3; // SyntaxError
__Object.hasOwn()__: альтернатива метода Object.hasOwnProperty(), позволяющая определять наличие в объекте указанного свойства. Работает лучше в некоторых крайних случаях:
const obj = { name: 'test' };
console.log(Object.hasOwn(obj, 'name')); // true
console.log(Object.hasOwn(obj, 'gender')); // false
__Причина ошибки / Error cause__: при повторном выбросе исключения (re-throwing) в качестве второго аргумента в конструктор Error можно передать объект со свойством cause, значением которого является оригинальное исключение:
try {
try {
connectToDatabase();
} catch (err) {
throw new Error('Не удалось подключиться к базе данных.', { cause: err });
}
} catch (err) {
console.log(err.cause); // ReferenceError: connectToDatabase is not defined
}
На этом перевод первой части, посвященной возможностям JS, завершен. В следующей части мы поговорим о возможностях TS.
Надеюсь, вы узнали что-то новое и не зря потратили время.
Happy coding!
Возможности JavaScript и TypeScript последних лет. Часть 1
Hello, world! Представляю вашему вниманию перевод первой части этой замечательной статьи , посвященной возможностям JS и TS последних трех лет, которые вы могли пропустить. В первой части мы поговорим...
habr.com