Object.entries + fromEntries против for: безопасная фильтрация конфига
-
Когда ты подтягиваешь конфиг с API, хочется быстро отфильтровать лишнее и не получить проблемы с прототипом. Старый подход с циклом for — это граница между “работает” и “хак”. Современный способ через Object.entries и Object.fromEntries выглядит элегантнее и безопаснее. Давай разбираться, в чём на самом деле фишка.
Вещи, которые обычно ломают новичков: забывают про наследование из прототипа, создают утечки памяти через случайные мутации, пишут столько условий, что код становится нечитаемым. Здесь речь именно про эти граблями.
Проблема старого подхода: цикл for и его сюрпризы
Цикл for — это универсальный солдат, но он универсален ровно настолько, насколько ты внимателен. Когда ты перебираешь объект через for…in, браузер не только проходит по собственным свойствам, но и лезет в прототип. Это может привести к неожиданным свойствам в результате, если у объекта есть наследованные члены.
Ещё классика: если ты мутируешь исходный объект или создаёшь новый через литерал
{}, ты неявно наследуешься от Object.prototype. И если где-то в коде кто-нибудь добавит property на Object.prototype (чего делать не стоит, но бывает), оно внезапно появится везде. Вот такие сюрпризы в боевом коде стоят ночных разборов.// Опасный подход const config = { api: 'prod', debug: false, timeout: 5000 }; const filtered = {}; for (const key in config) { // Если в прототипе есть что-то наследованное — попадет сюда if (config[key] !== null && config[key] !== undefined) { filtered[key] = config[key]; } } // Если Object.prototype.inherited = 'value' — попадет в filtered!Вопрос очень простой: зачем усложнять себе жизнь, если есть методы, которые работают только с собственными свойствами?
Object.entries: переход от объекта к массиву пар
Object.entries() делает одно, но делает хорошо: возвращает массив пар [ключ, значение] исключительно для собственных свойств объекта. Прототип остаётся за бортом. Это чистая структура, с которой можно работать как с массивом - фильтровать, маппить, сортировать.
Что даёт такой подход? Во-первых, декларативность: ты видишь, что нужно делать, прямо в коде. Во-вторых, неизменяемость по умолчанию: исходный объект остаётся нетронутым, потому что ты работаешь с копией. В-третьих, стандартные методы массива работают так, как ты их знаешь, без подвохов.
const serverConfig = { host: 'localhost', port: 3000, debug: true, secret: null, timestamp: undefined }; const entries = Object.entries(serverConfig); console.log(entries); // [ // ['host', 'localhost'], // ['port', 3000], // ['debug', true], // ['secret', null], // ['timestamp', undefined] // ] // Фильтруем: убираем null и undefined const filtered = entries.filter(([key, value]) => value != null); console.log(filtered); // [ // ['host', 'localhost'], // ['port', 3000], // ['debug', true] // ]Видишь разницу? Нет никаких скрытых свойств из прототипа, нет проверок на hasOwnProperty(). Просто берёшь то, что есть, и работаешь.
Object.fromEntries: замыкаем круг
Object.fromEntries() — это обратная сторона медали. Если Object.entries() разворачивает объект в массив пар, то fromEntries() собирает его обратно. Вот тут и рождается красивая паттерна: преобразование - операция - обратное преобразование.
Зачем это нужно? После фильтрации или маппинга у тебя остаётся массив пар. Чтобы вернуться к объекту и дальше его использовать в коде, нужна reverse-операция. А Object.fromEntries() гарантирует, что результат будет чистый объект без наследования, без лишних ссылок.
// Фильтруем и возвращаем объект const config = { api: 'https://api.example.com', timeout: 5000, debug: null, retry: undefined }; const cleaned = Object.fromEntries( Object.entries(config).filter(([key, value]) => value != null) ); console.log(cleaned); // { api: 'https://api.example.com', timeout: 5000 } // Или трансформируем значения const transformed = Object.fromEntries( Object.entries(config).map(([key, value]) => [ key.toUpperCase(), typeof value === 'string' ? value.toUpperCase() : value ]) ); console.log(transformed); // { API: 'HTTPS://API.EXAMPLE.COM', TIMEOUT: 5000, DEBUG: null, RETRY: undefined }Этот паттерн работает не только с объектами. Object.fromEntries() принимает любой iterable - Map, Array, даже генератор. Это даёт гибкость при работе с разными источниками данных.
Сравнение подходов: что выбрать?
Вот здесь важно понять, какой способ подходит под конкретную задачу. Они решают её по-разному, с разными побочными эффектами.
Подход Плюсы Минусы Когда использовать for…in Простой синтаксис, привычный Нужна проверка hasOwnProperty(), может поймать наследованные свойства, мутирует исходный объект если не осторожен Редко, если вообще нужен Object.entries() + filter/map + fromEntries() Безопасно от прототипа, декларативно, неизменяемо, стандартные методы массива Немного медленнее на огромных объектах (но это не критично), нужна поддержка ES2019 Всегда, это современный стандарт Object.keys() + цикл Избегаешь наследования, понятнее for…in Нужно создавать новый объект, всё равно требует условий Если нужна максимальная производительность (очень редко) Звучит как реклама Object.entries, но это работает. Исключения есть: если ты работаешь с древним Internet Explorer (давай, кто-то же его ещё использует?) или нужна микрооптимизация в критичном месте. В остальных случаях это стандарт, от которого не стоит отскакивать.
Реальный случай: парсим конфиг с API
Представим ситуацию: пришёл ответ с API, там конфиг приложения. Часть полей пустые, часть не нужны на фронте, часть нужно преобразовать. Типичная задача для production-среды.
// Ответ от API - сырой, как есть const apiResponse = { appName: 'MyApp', apiEndpoint: 'https://api.prod.com', deprecatedField: null, internalSecret: '12345', userTimeout: 30000, adminPanel: undefined, theme: 'dark', experimentalFeature: null }; // Вариант 1: Убираем null и undefined const safeConfig = Object.fromEntries( Object.entries(apiResponse).filter(([key, value]) => value != null) ); console.log(safeConfig); // { appName: 'MyApp', apiEndpoint: '...', internalSecret: '12345', userTimeout: 30000, theme: 'dark' } // Вариант 2: Фильтруем по whitelist - безопаснее для security const allowedFields = ['appName', 'apiEndpoint', 'userTimeout', 'theme']; const whitelisted = Object.fromEntries( Object.entries(apiResponse).filter(([key]) => allowedFields.includes(key)) ); console.log(whitelisted); // { appName: 'MyApp', apiEndpoint: '...', userTimeout: 30000, theme: 'dark' } // Вариант 3: Преобразуем значения в нужный формат const processed = Object.fromEntries( Object.entries(apiResponse) .filter(([key, value]) => value != null) .map(([key, value]) => { // Преобразуем таймауты в секунды, если это нужно if (key.includes('Timeout') && typeof value === 'number') { return [key, Math.floor(value / 1000)]; } return [key, value]; }) ); console.log(processed); // { appName: 'MyApp', apiEndpoint: '...', userTimeout: 30, theme: 'dark', ... }Видишь, как это выглядит? Без петель, без условий в конце, весь трансформ в одной цепочке. И главное - результат гарантированно не содержит ничего, кроме того, что явно там оказалось. Никаких скрытых наследований, никаких утечек.
Производительность: стоит ли переживать?
Обычный вопрос: “Но ведь Object.entries() создаёт новый массив? Не медленнее ли?” Да, создаёт. Но в реальных приложениях это не имеет значения, если ты не фильтруешь объекты с миллионами свойств каждый кадр.
Если конфиг пришёл с API, это обычно десятки полей максимум. Даже сотни - это ничто для JS-движков. Где это действительно может быть проблемой - это когда ты делаешь это на каждый обновления UI, миллион раз в цикле. Но в таких случаях проблема не в Object.entries(), а в архитектуре, которая это позволяет.
Помни: оптимизируй то, что действительно медленно. Профилируй перед тем, как что-то оптимизировать. Object.entries() достаточно быстрый для 99% случаев.
// Если ты действительно параноик по производительности // (что обычно не нужно), можно использовать Object.keys() const config = { a: 1, b: 2, c: null }; const filtered = {}; for (const key of Object.keys(config)) { const value = config[key]; if (value != null) { filtered[key] = value; } } // Это немного быстрее, чем entries() + fromEntries(), но читаемость хужеЧто ещё нужно знать
Несколько моментов, которые часто упускают:
- Object.entries() не включает символические ключи - если у тебя есть символы как ключи, они останутся невидимыми. Это обычно хорошо, но иногда может быть неожиданно.
- Порядок свойств гарантирован - в современном JS порядок свойств в объекте совпадает с порядком их добавления (для строковых ключей). Object.entries() сохраняет этот порядок.
- fromEntries() создаёт новый объект - это значит, что ссылка на исходный объект теряется. Если тебе это важно, нужно переписать логику.
- Map и Object.fromEntries() - друзья - ты можешь перевести Map в объект и обратно, это работает отлично и часто решает проблемы с передачей данных между системами.
// Пример с Map const configMap = new Map([ ['host', 'localhost'], ['port', 3000] ]); const configObject = Object.fromEntries(configMap); console.log(configObject); // { host: 'localhost', port: 3000 } // И обратно const backToMap = new Map(Object.entries(configObject)); console.log(backToMap); // Map(2) { 'host' => 'localhost', 'port' => 3000 }Когда это экономит время и нервы
Основная ценность этого подхода не в экономии строк кода. Она в том, что ты точно знаешь, что получишь. Нет скрытых наследований, нет побочных эффектов, нет утечек памяти из-за случайных ссылок на данные. Это особенно важно, когда конфиг приходит извне - с API, конфиг-файла, от пользователя.
Второе - это читаемость для коллег. Когда разработчик смотрит на Object.entries().filter().map(), он сразу понимает, что тут фильтруется и трансформируется. Цикл for с кучей условий требует больше внимания, чтобы разобраться, что на самом деле происходит.
Третье - это устойчивость к багам. Если ты по ошибке добавишь что-то на Object.prototype, это не сломает твой код. Если кто-то из коллег забудет hasOwnProperty() в цикле, это не будет твоя проблема.
Получается, что Object.entries + fromEntries - это не про красоту кода, а про его надёжность.
Здравствуйте! Похоже, вас заинтересовала эта беседа, но у вас ещё нет аккаунта.
Надоело каждый раз пролистывать одни и те же посты? Зарегистрировав аккаунт, вы всегда будете возвращаться на ту же страницу, где были раньше, и сможете выбирать, получать ли уведомления о новых ответах (по электронной почте или в виде push-уведомлений). Вы также сможете сохранять закладки и ставить лайки постам, чтобы выразить свою благодарность другим участникам сообщества.
С вашими комментариями этот пост мог бы стать ещё лучше 💗
Зарегистрироваться Войти© 2024 - 2026 ExLends, Inc. Все права защищены.