6 передовых функций JavaScript, которые вы можете использовать сегодня
Быть программистом на JavaScript - прекрасное время. Веб-технологии развиваются все более быстрыми темпами, и поставщики браузеров больше не стесняются сразу же внедрять новые и инновационные функции. Этот сдвиг в развитии означает, что программистам необходимо постоянно обновлять свой набор навыков, чтобы оставаться конкурентоспособными на своей должности.
В этой статье мы рассмотрим шесть функций ES2020 и ES2021, которые недавно были реализованы в современных браузерах, и увидим, как они помогают разработчикам JavaScript писать менее подверженный ошибкам и более эффективный код.
BigInt
При работе с большими целыми числами в JavaScript нам часто приходится использовать сторонние библиотеки, потому что тип Number
не может безопасно представлять целочисленные значения больше 253.
Рассмотрим следующий пример:
console.log(9999999999999999); // => 10000000000000000
В этом коде 9999999999999999
округляется до 10000000000000000
, поскольку оно больше наибольшего целого числа, поддерживаемого типом Number
. Если вы не будете осторожны, такое округление может поставить под угрозу безопасность вашей программы.
Вот еще один пример:
// notice the last digit
9800000000000007 === 9800000000000008; // => true
К счастью, в ECMAScript недавно был представлен тип данных BigInt
, который обеспечивает простой способ представления целых чисел, превышающих поддерживаемый диапазон Number
.
BigInt
можно создать, добавив n
к целому числу.
Сравните:
console.log(9800000000000007n); // => 9800000000000007n
console.log(9800000000000007); // => 9800000000000008
Также можно использовать конструктор:
BigInt('9800000000000007'); // => 9800000000000007n
Теперь вы можете выполнять арифметические операции с большими целыми числами в стандартном JavaScript без использования обходного пути:
9999999999999999 * 3; // => 30000000000000000
// with BigInt, integer overflow won’t be an issue
9999999999999999n * 3n; // => 29999999999999997n
Важно понимать, что Number
и BigInt
два различных типа данных, и вы не можете сравнить их со строгим оператор равенства:
5n === 5; // => false
typeof 5n; // => bigint
typeof 5; // => number
Однако вы все равно можете использовать оператор равенства, потому что он неявно преобразует операнды в один и тот же тип перед сравнением:
5n == 5; // => true
Вы можете выполнять арифметические операции с BigInt
так же, как с Number
:
50n + 30n; // => 80n
50n - 30n; // => 20n
50n * 20n; // => 1000n
50n / 5n; // => 10n
56n % 10n; // => 6n
50n ** 4n; // => 6250000n
Операторы инкремента, декремента и унарного отрицания также работают должным образом. Но унарный оператор плюс (+
) является исключением, и его применение к BigInt
вызовет TypeError
:
let x = 50n;
++x; // => 51n
--x; // => 50n
-50n; // => -50n
+50n; // => TypeError: Cannot convert a BigInt value to a number
Нулевой оператор объединения
ES2020 добавляет к языку JavaScript еще один оператор короткого замыкания: нулевой оператор объединения (??
). Этот оператор отличается от существующих сокращающих операторов тем, что он проверяет, является ли его левый операнд нулевым (null
или undefined
), а не ложным.
Другими словами, ??
возвращает свой правый операнд, только если его левый операнд равен null
или undefined
:
null ?? 2; // => 2
undefined ?? 2; // => 2
0 ?? 2; // => 0
false ?? true; // => false
Логический оператор ИЛИ (||
), с другой стороны, возвращает правый операнд, если левый один из 0
, -0
, 0n
, false
, ""
(пустая строка), null
, undefined
или NaN
. Сравните:
null ?? 2; // => 2
undefined ?? 2; // => 2
0 ?? 2; // => 0
false ?? true; // => false
??
особенно удобен при установке значения по умолчанию для свойства или переменной. Например:
function Config(darkMode) {
this.darkMode = darkMode ?? true;
// …
}
new Config(null); // => {darkMode: true}
new Config(); // => {darkMode: true}
new Config(false); // => {darkMode: false}
Конструктор Config
предоставляет значение по умолчанию для свойства darkMode
в случае, если данное значение nullish
или значения не задано.
??
также полезно при работе с DOM API:
// querySelector() returns null when the element doesn’t exist in the document
const elem = document.querySelector('elem') ?? document.createElement('elem');
Имейте в виду, что при использовании ??
с другими сокращающими операторами в выражении вы должны обозначать порядок вычисления скобками, иначе код выдаст ошибку.
Круглые скобки также помогают читабельности кода:
false || (true ?? true); // no error
false || true ?? true; // => SyntaxError
Promise.any()
ES2015 представил объект обещания с двумя методами: Promise.all()
и Promise.race()
. ES2021 дополнительно расширяет асинхронные возможности JavaScript, добавляя Promise.any()
. Этот новый метод возвращает обещание, которое выполняется, когда выполняется одно из обещаний в данной итерации, или отклоняет, если все обещания отклоняются.
Вот как это работает:
const promise = Promise.any([
Promise.reject('Error'),
fetch('https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_92x30dp.png', {mode: 'no-cors'}).then(() => 'google.com'),
fetch('https://en.wikipedia.org/static/images/project-logos/enwiki.png', {mode: 'no-cors'}).then(() => 'wikipedia.org'),
fetch('https://s.w.org/images/home/swag_col-1.jpg?1', {mode: 'no-cors'}).then(() => 'w.org')
]);
promise.then((fastest) => {
// the first promise that fulfills
console.log(fastest);
}).catch((error) => {
console.log(error);
});
Этот код выполняет три запроса на выборку. Как только одно из обещаний исполняется, оно возвращает обещание, которое исполняется с ценностью от этого обещания. Promise.any()
отличается от Promise.race()
тем, как он обрабатывает отказ. Обещание, возвращаемое Promise.any()
отклоняется только в том случае, если все обещания в отклонены:
const promise = Promise.any([
Promise.reject('Exception1'),
Promise.reject('Exception2'),
Promise.reject('Exception3')
]);
promise.then((response) => {
// ...
}).catch((e) => {
console.log(e.errors);
});
// logs:
// => ["Exception1", "Exception2", "Exception3"]
Обратите внимание, как значение отклонения всех обещаний передается в виде массива методу catch()
. Кроме того, вы можете использовать async и await для обработки результата:
(async () => {
try {
result = await Promise.any([
Promise.reject('Exception1'),
Promise.reject('Exception2'),
Promise.resolve('Success!')
]);
console.log(result);
} catch(e) {
console.log(e);
}
})();
// logs:
// => Success!
Promise.allSettled()
Еще один полезный метод, который недавно был добавлен - это Promise.allSettled()
. Этот метод, который дополняет существующий Promise.all()
, предназначен для возврата результата всех обещаний - как отклоненных, так и выполненных.
Вот пример:
const p1 = Promise.resolve('Success');
const p2 = Promise.reject('Exception');
const p3 = Promise.resolve(123);
Promise.allSettled([p1, p2, p3]).then((response) => {
response.forEach(result => console.log(result.value || result.reason))
});
// logs:
// => Success
// => Error!
// => 123
Обратите внимание, как результат всех обещаний передается в виде массива в функцию then()
. Внутри функции then()
метод forEach()
выполняет циклический перебор элементов массива. Если левый операнд оператора ||
не определен, он будет записан в консоль. В противном случае обещание было отклонено, и правильный операнд будет записан в журнал.
Для сравнения: Promise.all()
немедленно бросает reject, как только одно из обещаний отклоняется.
Необязательный оператор цепочки
Необязательный оператор цепочки (?.
) позволяет получить доступ к вложенному свойству без проверки каждого свойства в цепочке.
Рассмотрим следующий пример:
const obj = {};
const nickname = obj?.user?.profile?.nickname;
console.log(nickname); // => undefined
Этот код пытается присвоить значение вложенного свойства константе. Но такого свойства нет в obj
. К тому же user
и profile
не существует. Но благодаря необязательному оператору связывания код возвращается undefined
, а не выдает ошибку.
С обычным оператором цепочки вы получите ошибку:
const obj = {};
const nickname = obj.user.profile.nickname;
console.log(nickname); // => TypeError
Необязательный оператор цепочки также можно использовать при вызове метода объекта:
const obj = {};
const value = obj.myMethod?.();
console.log(value); // => undefined
Здесь myMethod
не существует в obj
; однако, поскольку он вызывается с использованием необязательного оператора цепочки, возвращаемое значение - undefined
. Опять же, с обычным оператором цепочки вы получите ошибку.
Но что, если вы хотите получить доступ к свойству динамически? Маркер ?.[]
позволяет ссылаться на переменную с помощью квадратных скобок.
Вот как это работает:
const obj = {
user: {
id: 123
}
};
const prop = 'nickname';
const nickname = obj?.user?.profile?.[prop];
console.log(nickname); // => undefined
code>globalThis
Хотя JavaScript был создан с целью выполнения сложных функций в веб-браузерах, теперь он может работать в совершенно разных средах, таких как серверы, смартфоны и даже роботы. Поскольку каждая среда имеет свою собственную объектную модель, для доступа к глобальному объекту вам потребуется другой синтаксис.
В среде браузера, вы можете использовать window
, frames
или self
. В Web Workers вы можете использовать self
. А в Node вы можете использовать global
. Это несоответствие усложняет веб-разработчикам написание переносимых программ на JavaScript.
globalThis
предоставляет единое универсальное свойство во всех средах для доступа к глобальному объекту:
// browser environment
console.log(globalThis); // => Window {...}
// web worker environment
console.log(globalThis); // => DedicatedWorkerGlobalScope {...}
// node environment
console.log(globalThis); // => Object [global] {...}
Раньше разработчикам приходилось писать дополнительные проверки, чтобы убедиться, что они ссылаются на правильное свойство. С globalThis
, это больше не требуется, и код будет работать как в window, так и в не window контекстах. Имейте в виду, что вам все равно может потребоваться использовать полифилл для обратной совместимости в старых браузерах.