JavaScript постоянно развивается: с каждым годом в нем появляются новые возможности, а устаревшие подходы уступают место более эффективным решениям. В этой статье мы рассмотрим ключевые современные практики, которые помогут вам писать чистый, надежный и производительный JavaScript-код.
Статья основана на публикации «JavaScript Best Practices».
Используйте let и const вместо var для объявления переменных
В устаревшем коде часто встречается объявление переменных с помощью ключевого слова var. Переменные, объявленные с помощью var, имеют функциональную область видимости. Хотя var всe ещe поддерживается, использование let и const предпочтительнее, поскольку они обеспечивают блочную область видимости, что делает код более предсказуемым и уменьшает вероятность неожиданных ошибок.
Пример с let – переменная j существует только внутри цикла:
Пример с var – переменная j существует вне цикла, что может привести к неожиданным ошибкам:
Классы вместо прототипов
Во многих старых кодовых базах или статьях про ООП в JavaScript можно встретить использование функции и прототипов для создания классов. Это был способ имитировать работу классов до их официального появления в языке.
Пример старого подхода с использованием прототипов:
Современный подход с использованием классов:
Используйте class вместо старого подхода с прототипами. Это стандартный, современный и читаемый способ создания объектов в JavaScript: классы поддерживают наследование, статические методы и приватные поля.
Приватные поля класса
Раньше JavaScript-разработчики часто использовали символ подчеркивания _ для обозначения приватных свойств или методов в классах. Однако это было всего лишь договоренностью между программистами и не обеспечивало реальной приватности.
Здесь свойство _name доступно из-за пределов класса, что делает его уязвимым к случайному или преднамеренному изменению:
JavaScript теперь поддерживает настоящие приватные поля, которые обозначаются с помощью символа #. Такие поля доступны только внутри класса и недосягаемы извне:
Используйте приватные поля с #, если вам нужно защитить данные внутри класса. Это делает код более безопасным, устойчивым и соответствует современным стандартам JavaScript.
Стрелочные функции
Стрелочные функции часто используются для создания компактных и читаемых анонимных функций или колбэков. Они особенно полезны при работе с функциями высшего порядка map, filter и reduce:
Почему стоит использовать стрелочные функции:
- Краткость. Стрелочные функции убирают лишний шаблонный код (ключевые слова function, return и фигурные скобки).
- Автоматическая привязка контекста this. Стрелочные функции не создают собственный контекст this. Вместо этого они используют контекст из окружающей области, где они были объявлены (лексическая область видимости).
Здесь стрелочная функция гарантирует, что this будет ссылаться на экземпляр класса Person, независимо от того, где вызывается метод getName:
Используйте стрелочные функции, когда нужно создать короткий и понятный колбэк или анонимную функцию. Они не только сокращают код, но и решают многие проблемы с привязкой контекста this, что делает их незаменимыми в современном JavaScript.
Старт карьеры в веб-разработке: Frontend Basic от Proglib Academy
Курс Frontend Basic от Proglib Academy предлагает 26 видеоуроков и 28 практических заданий по HTML, CSS и JavaScript, позволяющих за 2 месяца создать 4 реальных проекта для портфолио и получить навыки, востребованные на рынке веб-разработки с медианной зарплатой от 98 000 рублей.
(function () { let link = document .getElementById ("09f6c35e-b8d7-449d-9cc5-72990a41b93e-https://clc.to/_iBn0A-31"); if (! link) return; let href = link .getAttribute ("href"); if (! href) return; let prefix = link .dataset .prefix; let action = link .dataset .action; link .addEventListener ("click", function (e) { let data = new FormData (); data .append ("url", href); apiFetch (action, { method: "POST", body: data }) .then (function (res) {}) .catch (function (err) { console .error (err); }); }) })();
Оператор нулевого слияния
Раньше JavaScript-разработчики часто использовали логический оператор || для задания значений по умолчанию в случае, если переменная имеет значение undefined или null. Однако у этого подхода есть побочный эффект: || воспринимает значения 0, false или пустая строка "", как ложные и заменяет их на значение по умолчанию:
Оператор нулевого слияния ?? появился для того, чтобы корректно обрабатывать подобные ситуации. Он проверяет только на null или undefined и не заменяет такие ложные значения, как 0, false или "":
Используйте ??, если хотите задавать значения по умолчанию только для случаев null или undefined. Это делает код более предсказуемым и надежным.
Опциональная цепочка
При работе с глубоко вложенными объектами или массивами в JavaScript часто возникает необходимость проверять существование каждого уровня вложенности перед попыткой получить значение из следующего уровня. До появления оператора ?. (опциональной цепочки) для упрощенного доступа к вложенным свойствам объектов или массивов это приводило к громоздкому и повторяющемуся коду. Здесь нам приходится вручную проверять, существует ли свойство price, перед доступом к price.tax, чтобы избежать ошибок:
Оператор ?. автоматически проверяет, существует ли свойство или метод, прежде чем попытаться к нему обратиться. Если на каком-либо уровне цепочки встречается null или undefined, оператор возвращает undefined, не выбрасывая ошибку:
Оператор ?. нужно использовать потому, что он:
- Сокращает объем шаблонного кода и упрощает работу с глубоко вложенными структурами.
- Снижает риск появления ошибок, так как позволяет аккуратно обрабатывать значения null или undefined.
- Улучшает читаемость и удобство поддержки кода, особенно при работе с динамическими данными или сложными объектами.
⚛️🧹 React без воды: 20 техник чистого кода
От условного рендеринга до деструктуризации — разбираем приемы, которые сделают ваш код более профессиональным и поддерживаемым.
async/await
В старом JavaScript для работы с асинхронными операциями часто использовались колбэки или цепочки промисов, что быстро превращало код в сложный и трудночитаемый. Например, использование .then() для цепочек промисов делало логику менее наглядной, особенно при большом количестве асинхронных операций:
Используйте async и await, чтобы асинхронный код выглядел как обычный синхронный. Это улучшает читаемость и упрощает обработку ошибок с помощью try...catch:
Синтаксис async/await упрощает работу с асинхронным кодом, убирая необходимость в использовании цепочек .then() и .catch(). Это делает код:
- Читаемым – он становится похож на обычный последовательный код.
- Поддерживаемым – легче разобраться в логике, особенно при большом количестве асинхронных вызовов.
- Удобным для обработки ошибок– конструкция try...catch упрощает отладку и поддержку.
Взаимодействие с ключами и значениями объектов
В старом JavaScript для работы с ключами и значениями объектов часто использовались циклы, например, for...in или Object.keys(), с последующим доступом к значениям через скобочную или точечную нотацию. Такой подход может приводить к избыточному коду:
Используйте современные методы Object.entries(), Object.values() и Object.keys(), чтобы упростить работу с объектами. Эти методы возвращают удобные структуры данных (например, массивы), делая код более лаконичным и понятным:
Методы Object.entries(), Object.values() и Object.keys() нужно использовать потому, что они:
- Сокращают объем шаблонного кода, необходимого для перебора объектов.
- Удобны при работе с сложными или динамическими структурами данных.
- Позволяют легко преобразовывать объекты в другие структуры данных (например, массивы).
Как проверить, является ли переменная массивом
Раньше для проверки того, является ли переменная массивом, разработчики использовали различные сложные методы. Например, проверяли конструктор или использовали instanceof. Однако такие подходы часто были ненадежны, особенно при работе в разных контекстах выполнения (например, между iframe):
Используйте современный метод Array.isArray(), который обеспечивает простой и надежный способ проверить, является ли переменная массивом. Этот метод стабильно работает в любых средах и контекстах выполнения:
Map
Раньше в JavaScript для сопоставления ключей со значениями разработчики использовали обычные объекты {}. Однако у этого подхода есть ограничения, особенно если в качестве ключей нужно использовать не строки и не символы.
Дело в том, что обычные объекты могут использовать только строки или символы в качестве ключей. Если попытаться использовать объект или массив, он автоматически преобразуется в строку, что может привести к неожиданному поведению:
Используйте Map, если вам нужно хранить ключи любого типа (включая объекты и массивы) или если вам требуется более гибкая структура данных:
Преимущества Map:
- Позволяет использовать любой тип данных в качестве ключа, включая объекты и массивы.
- Сохраняет порядок добавленных элементов, в отличие от обычных объектов.
- Обеспечивает быстрый поиск и доступ к данным, особенно в больших коллекциях.
🧩⚛️ React + структуры данных = суперкод: 7 примеров для практики
Представляешь, оказывается, если правильно подобрать структуру данных, можно не только заставить приложение летать, но и сделать код настолько чистым и красивым, что глаз не оторвать. Серьезно, это как убраться в квартире – и жить приятнее, и гостей не стыдно позвать. Причем, это не какая-то там магия – это просто грамотный подход, который может освоить каждый разработчик, было бы желание!
Symbol для скрытых значений
В JavaScript объекты обычно используются для хранения пар «ключ-значение». Однако если вам нужно добавить «скрытые» или уникальные свойства в объект без риска конфликтов имен с другими свойствами, или если вы хотите, чтобы эти свойства не были доступны извне, можно использовать Symbol.
Что делает Symbol:
- Создает уникальные ключи, которые нельзя случайно перезаписать.
- Эти ключи не отображаются при переборе объекта (for...in, Object.keys()).
- Символы не превращаются в строки, поэтому их нельзя случайно перезаписать другим свойством.
При использовании Symbol скрытые свойства видны только через Object.getOwnPropertySymbols():
Преимущества Symbol:
- Предотвращает конфликты имен – два символа никогда не будут равны, даже если имеют одинаковое описание.
- Инкапсуляция – скрытые свойства не мешают другим частям кода и не перезаписываются случайно.
- Полезно в библиотеках и фреймворках – можно хранить метаданные или внутреннее состояние объекта без влияния на основную логику.
Ознакомьтесь с возможностями Intl API перед использованием сторонних библиотек
Раньше разработчики часто использовали сторонние библиотеки для форматирования дат, чисел и валют в разных локалях. Хотя такие библиотеки обладают мощным функционалом, они:
- Увеличивают размер проекта.
- Дублируют возможности, уже встроенные в JavaScript.
Перед установкой библиотеки попробуйте встроенный API Intl, который поддерживает форматирование:
- Чисел
- Дат
- Валют
- Других локализованных данных
Примеры использования Intl
Форматирование валюты без сторонних библиотек:
Форматирование дат:
Используйте строгое равенство === по возможности
Одной из самых сложных и удивительных особенностей JavaScript является поведение оператора нестрогого равенства ==. Этот оператор выполняет приведение типов, пытаясь привести операнды к одному типу перед сравнением. Это может приводить к странным и неожиданным результатам, например:
Используйте строгое равенство === вместо нестрогого ==, если это возможно:
- Строгое равенство === не выполняет приведение типов.
- Оно сравнивает и значение, и тип данных напрямую, что обеспечивает более предсказуемое поведение.
Вот еще пример различия между == и ===:
Почему важно использовать ===:
- Избежание неожиданных ошибок. Приведение типов в == может быть непредсказуемым, особенно при работе с разными типами данных (числа, строки, булевы значения).
- Более читаемый и надежный код. Использование === делает ваши сравнения предсказуемыми и уменьшает вероятность появления скрытых багов.
Явная обработка выражений в if-условиях
В JavaScript оператор if автоматически преобразует результат выражения в истинное (truthy) или ложное (falsy) значение. Это означает, что следующие значения считаются ложными (falsy):
- 0
- "" (пустая строка)
- null
- undefined
- false
- NaN
Все остальные значения считаются истинными (truthy), даже такие, как пустой массив [] или пустой объект {}. Неявное приведение типов может повлечь за собой неожиданные проблемы, например:
Чтобы избежать неожиданных результатов, лучше явно указывать условия в if.
1. Проверка на конкретное значение:
2. Проверка на null или undefined:
Явная проверка особенно важна, когда мы работаем со значениями, которые могут принимать 0, false, null или "", так как их автоматическое преобразование может привести к нежелательному поведению.
Не используйте встроенный Number для точных вычислений
В JavaScript встроенный тип Number представляет собой число с плавающей запятой в формате IEEE 754. Этот формат удобен и эффективен, но он может приводить к неточностям в вычислениях, особенно при работе с десятичными дробями. Здесь, например, значение не равно 0.3, как можно было бы ожидать, из-за особенностей двоичного представления чисел с плавающей запятой:
Такие неточности могут быть критичными, особенно если вы работаете с:
- Платежами и финансовыми расчетами.
- Бухгалтерскими отчетами.
- Криптовалютами.
Даже небольшая ошибка в вычислениях может привести к значительным потерям или юридическим проблемам. Для точных расчетов необходимо использовать специализированные библиотеки decimal.js или big.js:
Будьте осторожны с JSON и большими числами
В JavaScript есть ограничения на работу с очень большими числами. Максимальное безопасное целое число (Number.MAX_SAFE_INTEGER) – это 9007199254740991.
Числа больше этого значения могут терять точность, что может привести к ошибкам, особенно при взаимодействии с API или базами данных, где идентификаторы могут быть очень большими. Здесь, например, значение 9007199254740999 изменилось на 9007199254741000, потому что JavaScript не может точно представить числа больше MAX_SAFE_INTEGER:
Решение 1: Использование reviver в JSON.parse()
Можно явно обрабатывать большие числа и хранить их как строки, чтобы избежать потери точности:
Решение 2: Использование BigInt
JavaScript поддерживает BigInt, который позволяет безопасно работать с числами больше MAX_SAFE_INTEGER:
Но у BigInt есть недостаток – его нельзя сериализовать в JSON напрямую:
Чтобы сериализовать BigInt, нужно конвертировать его в строку:
⚠️ Важно: клиент и сервер должны согласовывать формат данных!
Если сервер отправляет id как строку ("9007199254740999"), клиент должен корректно обработать это значение. При передаче BigInt в JSON обе стороны (сервер и клиент) должны быть согласны, как именно передавать и интерпретировать большие числа.
Используйте JSDoc для удобства чтения и редактирования кода
В JavaScript функции и объекты часто не имеют документации, что затрудняет понимание кода. Это может быть проблемой как для других разработчиков, так и для вас в будущем. Вот пример функции без документации:
Без пояснений неясно:
- Какие свойства есть у объекта user?
- Есть ли у него middleName?
- Следует ли использовать surName вместо lastName?
JSDoc позволяет явно описывать структуры объектов, параметры функций и их возвращаемые значения.
Перепишем показанный выше пример с JSDoc:
Теперь ясно, какие свойства ожидаются в user, какие обязательны, а какие нет.
Используйте тесты
С увеличением объема кодовой базы становится все сложнее проверять вручную функциональность и корректность всех изменений. Автоматизированные тесты гарантируют, что код работает правильно, и позволяют вносить изменения без риска сломать что-то важное.
В JavaScript существует множество инструментов для тестирования (Jest, Mocha и т. д.), но начиная с Node.js 20, внешний фреймворк не нужен – появился встроенный тестовый раннер.
Простой пример с node:test:
Чтобы запустить тесты, просто выполните:
Для тестирования пользовательских сценариев в браузере отлично подходит библиотека Playwright. С ее помощью можно автоматизировать взаимодействие с сайтом в разных браузерах (Chrome, Firefox, Safari).
Пример теста с Playwright:
Альтернативные JavaScript-окружения Bun и Deno также имеют встроенные тестовые фреймворки. Пример теста в Deno:
Почему необходимо перейти на автоматизированное тестирование:
- Автоматические тесты экономят время – ошибки находятся на ранних стадиях, а не после релиза в продакшн.
- Уверенность в коде – можно смело вносить изменения, зная, что тесты проверят работоспособность.
- Современные инструменты упрощают тестирование – теперь писать тесты можно без сложной настройки.
В заключение
Следование лучшим практикам помогает избежать распространенных ошибок, повысить безопасность и производительность приложений. В 2025 году важно не просто знать язык, но и применять его возможности наилучшим образом, чтобы создавать надежные, масштабируемые и легко поддерживаемые проекты. Чтобы быть в курсе всех изменений, обязательно следите за обновлениями ECMAScript и рабочей группой TC39, которая предлагает и разрабатывает новые функции JavaScript.