Главные изменения JavaScript в 2026 году
- суббота, 31 января 2026 г. в 00:00:05

В 2026 году обновят JavaScript. Окончательный список изменений сформируется из проектов ECMAScript, достигших 4 этапа к марту. Но многие из них уже на заключительной стадии, а другие готовы и доступны в некоторых браузерах и средах. Под катом — что ждёт разработчиков и какие проблемы будут решены в этом апдейте.
Информация об изменениях — отсюда.
Temporal — долгожданная замена проблемного объекта Date в JavaScript. Он уже достиг стадии 3 в TC39, и для него появились первые полноценные реализации. Это реальный кандидат на включение в ECMAScript 2026: ожидается, что Temporal достигнет стадии 4 в первом квартале 2026 года.
Объект Date исторически считается одним из самых неудачных API в JavaScript. Он сочетает сразу несколько разных концепций в одном объекте — дату, время, временную зону и форматирование, из‑за чего его поведение трудно предсказать. Методы Date мутируют состояние объекта, зависят от локали и реализации движка, по‑разному обрабатывают строки и числа и часто приводят к скрытым ошибкам. Работа с часовыми поясами в Date крайне ограничена, а корректная интернационализация фактически невозможна без сторонних инструментов. В результате разработчики используют Date неправильно или стараются полностью избегать его.
Чтобы компенсировать эти недостатки, проекты годами полагались на сторонние библиотеки вроде Moment.js, date-fns или Luxon. Но такие решения значительно увеличивают размер бандла, особенно если требуется поддержка часовых поясов и локалей. В реальных веб‑приложениях библиотеки для работы с датами нередко оказываются одной из самых «тяжёлых» зависимостей, несмотря на то что значительная часть необходимой логики уже присутствует в самих браузерах.
Temporal решает эти проблемы на уровне языка:
Его API строгий, явный и устойчивый к ошибкам.
Разные типы данных разделены по смыслу: дата, время, момент во времени, интервал и календарь представлены отдельными объектами.
Temporal имеет полноценную поддержку часовых поясов и календарей, корректно парсит и форматирует значения и предоставляет предсказуемое, неизменяемое поведение.
Всё это делает код более читаемым и существенно снижает вероятность ошибок.
Масштаб Temporal отражает сложность задачи, которую он решает. Если для проверки типичных возможностей ECMAScript делают сотни тестов, то для Temporal — тысячи тестов: он охватывает область, которая раньше целиком лежала на плечах пользовательских библиотек. Спецификация долго согласовывалась и постепенно упрощалась, а сейчас находится на этапе финальной полировки.
Реализация Temporal уже существует в Firefox. Команда V8 совместно с разработчиками Boa работает над библиотекой temporal_rs на Rust, которая содержит основную логику и может переиспользоваться разными JavaScript‑движками. Этот подход снижает стоимость внедрения новой функциональности и ускоряет её распространение. Temporal уже используют и другие движки, такие как Kiesel и Yavashark.
На данный момент библиотека доступна в версии 0.1, что допускает финальные изменения в спецификации, хотя она уже проходит все актуальные тесты и готова к практическому использованию. Temporal больше не считается экспериментальной функцией в V8 и ожидается в Chromium 144. Реализация для Safari активно развивается в репозитории WebKit, а также доступны два полифилла, пригодных для продакшена. Движки Ladybird и Graal в целом тоже готовы.
// === Date ===
// Встреча 31 января 2026 в 10:00 (таймзона неявная!)
const meetingDate = new Date(2026, 0, 31, 10, 0);
// Добавляем 1 месяц (мутирует объект)
meetingDate.setMonth(meetingDate.getMonth() + 1);
console.log('Date:', meetingDate.toString());
// Результат зависит от локальной таймзоны
// Возможен неожиданный сдвиг даты
// === Temporal ===
// Та же встреча, но явно в Europe/Berlin
const meetingTemporal = new Temporal.ZonedDateTime(
2026, 1, 31,
10, 0, 0, 0, 0, 0,
'Europe/Berlin'
);
// Добавляем 1 месяц (не мутирует объект)
const nextMonth = meetingTemporal.add({ months: 1 });
console.log('Temporal:', nextMonth.toString());
// 2026-02-28T10:00:00+01:00[Europe/Berlin]
Ещё одно перспективное предложение, достигшее этапа 3 — отложенный импорт (import defer). Он нацелен на улучшение производительности модульной системы JavaScript и с большой вероятностью войдёт в ECMAScript 2026.
Обычный статический импорт загружает и вычисляет модуль сразу при инициализации. А отложенный — возвращает дескриптор пространства имён модуля без немедленной загрузки его кода. Таким образом, фактическая загрузка и выполнение происходят только в момент первого обращения к свойствам этого пространства имён. А приложение платит за выполнение модуля лишь тогда, когда он действительно используется.
Этот подход особенно эффективен в крупных кодовых базах с глубокими и сложными графами зависимостей, где инициализация модулей может занимать сотни миллисекунд и заметно замедлять запуск приложения. Отложенный импорт позволяет существенно сократить время старта, не меняя архитектуру приложения и не усложняя порядок загрузки модулей.
В JavaScript уже существует динамический импорт (import()), однако он всегда асинхронен и требует сетевого запроса в момент вызова. Это делает его не всегда применимым для существующего кода, особенно там, где API и логика выполнения рассчитаны на синхронную модель.
Использование import defer не нарушает синхронный поток выполнения, поэтому его проще внедрять точечно как оптимизацию, не переписывая значительные части приложения.
Подобный шаблон уже применяется в крупных продуктах, включая терминал Bloomberg, который сочетает C++ и JavaScript. В таких системах заранее определить оптимальный порядок загрузки модулей крайне сложно даже с помощью компиляторов. Возможность позволить коду запускаться сразу, а зависимости подгружать только по мере необходимости, показала себя как эффективная стратегия оптимизации.
Отложенный импорт модулей с помощью import defer уже поддерживается рядом инструментов экосистемы, включая TypeScript, Babel, Prettier и webpack. Ожидается, что поддержка в браузерах появится в течение 2026 года, что сделает эту возможность практическим инструментом для ускорения загрузки крупных веб‑приложений.
// main.js
import defer * as analytics from './analytics.js';
console.log('Приложение стартовало');
// Модуль будет загружен только при первом обращении
button.addEventListener('click', () => {
analytics.track();
});
Один из наиболее часто используемых методов Array — Array.from. Он позволяет создать копию Map, Set или чего-то ещё, что выглядит как массив. Метод работает только с синхронными итерируемыми объектами, которые сразу возвращают значения, а не с асинхронными, где значения обёрнуты в промисы и используются для работы с асинхронными операциями (сетевые запросы, файлы, события, API). Для асинхронных данных понадобится либо очень популярная библиотека, либо Array.fromAsync, который упрощает чтение (и написание) кода и уже доступен во всех браузерах и средах выполнения JavaScript, таких как Node и Deno.
На самом деле метод Array.from существует уже достаточно давно, чтобы стать частью Baseline 2024. До сих пор подробности о том, как он работает, не были добавлены в стандарт ECMAScript: сначала исправляли ошибку в реализации, а затем — решали вопросы согласованности с остальным языком. Уже достигнуто соглашение о том, что он перейдёт на этап 4, как только редакторы TC39 одобрят изменения в спецификации, что должно произойти к выходу ECMAScript 2026.
async function * asyncGen (n) {
for (let i = 0; i < n; i++)
yield i * 2;
}
// `arr` будет `[0, 2, 4, 6]`.
const arr = [];
for await (const v of asyncGen(4)) {
arr.push(v);
}
// эквивалентно.
const arr = await Array.fromAsync(asyncGen(4));
Для преобразования JSON в рабочий объект данных используется JSON.parse(). Но при работе с числами и датами это преобразование происходит с потерями: JSON поддерживает числа произвольной точности, а JavaScript — нет. Например, разбор квинтиллиона (1000000000000000000) и числа на единицу меньше (999999999999999999) даст одинаковый результат — 1000000000000000000.
При анализе даты и времени тоже есть проблемы: JavaScript может некорректно интерпретировать строковое представление. Кроме того, язык не позволяет преобразовать BigInt в JSON, поскольку обратное преобразование не позволит получить исходное значение.
Предложение JSON.parse Source Text позволяет получить исходный код JSON и преобразовать его в нужный формат — либо в BigInt, либо с возможностью просмотра экранированных символов, чтобы можно было составить строку именно так, как нужно.
Впервые это предложение выдвинули в 2018 году, и тогда же активно обсуждалось добавление дополнительных функций, например, сериализации объектов JavaScript в JSON (обратный процесс синтаксического анализа). Однако в предложении, которое дошло до четвёртого этапа, этого нет.
const digitsToBigInt = (key, val, {source}) =>
/^[0-9]+$/.test(source) ? BigInt(source) : val;
const bigIntToRawJSON = (key, val) =>
typeof val === "bigint" ? JSON.rawJSON(String(val)) : val;
const tooBigForNumber = BigInt(Number.MAX_SAFE_INTEGER) + 2n;
JSON.parse(String(tooBigForNumber), digitsToBigInt) === tooBigForNumber;
// → true
const wayTooBig = BigInt("1" + "0".repeat(1000));
JSON.parse(String(wayTooBig), digitsToBigInt) === wayTooBig;
// → true
const embedded = JSON.stringify({ tooBigForNumber }, bigIntToRawJSON);
embedded === '{"tooBigForNumber":9007199254740993}';
// → trueВ JavaScript появится механизм using, предназначенный для явного и надёжного управления ресурсами: файлами, сетевыми соединениями или подключением к базе данных, которые важно освобождать строго вовремя, а не полагаться на сборщик мусора.
Раньше для этого обычно использовалась конструкция try / finally: ресурс создавался в блоке try, а очищался в блоке finally, чтобы гарантировать освобождение даже при ошибке или раннем return. На практике такой код было трудно читать, особенно в больших функциях: разработчику приходилось прокручивать файл вверх и вниз, чтобы убедиться, что ресурс действительно закрывается. Это создавало риск пропустить очистку или просто надеяться, что она где‑то есть.
function readFile(path) {
const file = openFile(path);
try {
return file.read();
} finally {
file.close(); // важно не забыть
}
}Механизм using решает эту проблему. Вместо объявления ресурса через const используется блок using, и очистка происходит автоматически в момент выхода из области видимости. Если объект определяет метод Symbol.dispose или Symbol.asyncDispose, он будет вызван независимо от того, как завершился код — успешно, с ошибкой или через return. Такой подход делает управление ресурсами более понятным и предсказуемым и напоминает аналогичные механизмы из C++, C# и Python.
class FileHandle {
constructor(path) {
this.file = openNativeFile(path);
}
read() {
return this.file.read();
}
[Symbol.dispose]() {
this.file.close();
}
}
function readFile(path) {
using file = new FileHandle(path);
return file.read();
} // file.close() будет вызван автоматически
Главное преимущество нового способа в том, что он обеспечивает детерминированную очистку. В отличие от сборщика мусора, который работает непредсказуемо и ориентируется только на потребление памяти, using освобождает ресурсы строго в нужный момент. Это особенно важно, например, для подключений к базе данных, где существуют жёсткие лимиты и ожидание сборщика мусора может привести к сбоям.
Механизм уже доступен в Chrome, Node.js, Deno и ряде других сред, а также поддерживается такими инструментами, как TypeScript и Babel.
// все последующие вызовы возвращают true
Error.isError(new Error());
Error.isError(new TypeError());
Error.isError(new DOMException());
try {
1 + 1n;
} catch (e) {
console.log(Error.isError(e)); // Операция вызвала ошибку TypeError, поэтому возвращается значение true.
}
// все последующие вызовы возвращают false
Error.isError();
Error.isError({});
Error.isError(null);
Error.isError(undefined);
Error.isError(17);
Error.isError("Error");
Error.isError(true);
Error.isError(false);
// Это не ошибка, потому что у объекта нет приватного поля,
// инициализированного конструктором Error.
Кроме того, во многих других языках уже есть встроенный способ объединения последовательности итераторов, чтобы можно было вызывать их один за другим и получать значения из них всех сразу и в правильном порядке. Сейчас в JavaScript это можно сделать только с помощью генераторов. Новое предложение по объединению итераторов упрощает эту задачу с помощью новой функции Iterator.concat. Она уже доступна в Firefox и Safari.
Кроме того, во многих других языках уже есть встроенный способ объединения последовательности итераторов, чтобы можно было вызывать их один за другим и получать значения из них всех сразу и в правильном порядке. Сейчас в JavaScript это можно сделать только с помощью генераторов. Новое предложение по объединению итераторов упрощает эту задачу с помощью новой функции Iterator.concat. Она уже доступна в Firefox и Safari.
let digits = Iterator.concat(lows, [4, 5], highs);Небольшое, но полезное улучшение — операция getOrInsert для Map и WeakMap.
Во многих языках программирования существует встроенный способ «обновить значение или вставить его, если ключ отсутствует», без явной проверки на существование. В JavaScript такая операция до сих пор требует дополнительного условного кода.
Новый метод upsert объединяет обновление и вставку в одну атомарную операцию, упрощая код и снижая вероятность ошибок. Ожидается, что она появится в Chrome 145 в начале 2026 года и, вероятно, достигнет стадии 4 весной того же года.
// Сейчас
let prefs = getUserPrefsMap();
if (!prefs.has("useDarkmode")) {
prefs.set("useDarkmode", true); // по умолчанию true
}
// Используя getOrInsert
let prefs = getUserPrefsMap();
prefs.getOrInsert("useDarkmode", true); // по умолчанию trueВ JavaScript сложные действия, например суммирование чисел, могут давать неточные результаты. Это связано с использованием 64-битных чисел с плавающей запятой. Встроенный объект Math не содержит готового метода для точного сложения, а использование циклов может накапливать погрешность округления. Особенно при работе с числами, которые не могут быть точно представлены в 64-битном формате.
Новый метод — Math.sumPrecise даёт более точные результаты для сложения чисел с плавающей запятой благодаря более эффективному (но более медленному) алгоритму. Как утверждает Эрик Мейер, специалист по веб-стандартам в Igalia, новый алгоритм не устраняет сложности со сложением в JavaScript, но значительно упрощает выполнение некоторых задач по сравнению с написанием собственной функции».
console.log(Math.sumPrecise([1, 2]));
// вывод: 3
console.log(Math.sumPrecise([1e20, 0.1, -1e20]));
// вывод: 0.1
В JavaScript существуют типы для работы с двоичными данными, например, Uint8Array. Но до сих пор в языке нет встроенного способа кодировать эти данные в Base64 или, наоборот, создавать массив байтов из Base64‑ или шестнадцатеричных строк. Приходится использовать нестандартные API или сторонние библиотеки, например, при работе с SSH‑ключами или при встраивании небольших изображений на страницу.
Этот пробел закроет Proposal ArrayBuffer Base64, который добавит нативные методы для прямого преобразования бинарных данных в Base64 и обратно.
let string = 'SGVsbG8gV29ybGQ=';
console.log(Uint8Array.fromBase64(string));
// Uint8Array([72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100])
string = '48656c6c6f20576f726c64';
console.log(Uint8Array.fromHex(string));
// Uint8Array([72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100])Даже такой простой вопрос, как «какой сегодня первый день недели», не имеет универсального ответа. В одних странах неделя начинается с понедельника, в других — с воскресенья. Выходные тоже отличаются: где‑то это суббота и воскресенье, где‑то пятница и воскресенье, а в некоторых странах — всего один выходной день, который может приходиться на пятницу, субботу или воскресенье. Более того, официальные выходные со временем меняются. Если вы пишите календарное или планировочное приложение, все эти различия критичны.
JavaScript не заставляет разработчиков вручную хранить и обновлять такие данные. Вся информация берётся из стандарта Unicode Common Locale Data Repository (CLDR) и доступна через международное API Intl. Это позволяет приложениям корректно работать в любой стране без собственной логики локализации.
В ECMAScript 2026 это направление развивается дальше: API Intl.Locale получает расширенные возможности для работы с деталями локали — языком, регионом, письменностью, системой цифр и календарём — даже в тех случаях, когда для точной комбинации не существует отдельного ISO‑кода.
Например можно задать вот такую сложную модель:
const locale = new Intl.Locale('ca-Latn-ES-fonipa-valencia-u-nu-roman');
console.log(locale.language); // ca
console.log(locale.region); // ES
console.log(locale.numberingSystem); // romanЭта строка описывает каталанский язык с латинской письменностью, используемый в Испании, валенсийский вариант произношения, фонетическую запись и римскую систему чисел. Именно такие подробные и формальные описания позволяют браузеру корректно отображать даты, числа и текст так, как это принято в конкретном месте мира, без дополнительного кода со стороны разработчика.
В итоге ECMAScript 2026 выглядит скорее эволюционным, чем революционным релизом. В стандарте не ожидается по‑настоящему радикальных нововведений, и главным событием года остаётся долгожданный Temporal — важный, но во многом «наводящий порядок» инструмент, закрывающий давние боли работы с датами и временем. Остальные изменения аккуратно шлифуют язык и экосистему, делая JavaScript стабильнее и удобнее, но без громких прорывов.
Хочется верить, что это лишь временное затишье перед бурей. Возможно, ECMAScript 2026 станет тем самым спокойным годом, который подготовит почву для более смелых идей, и уже в следующем цикле мы наконец увидим давно обсуждаемые сигналы и другие фундаментальные механизмы прямо в ядре языка.
НЛО прилетело и оставило здесь промокод для читателей нашего блога:
-15% на заказ любого VDS (кроме тарифа Прогрев) — HABRFIRSTVDS.