<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Object.keys + map против for...in: баги с прототипами при нормализации API-данных]]></title><description><![CDATA[<p dir="auto">Нормализация данных из API часто ломается на неожиданных свойствах из прототипов. for…in тянет за собой унаследованные ключи, а Object.keys + map держит только свои. Это спасает от багов в валидации и трансформации.</p>
<p dir="auto">В реальном проекте API кидает объект с полями userId, name, email. Кажется, что for…in пройдется по ним чисто. Но если где-то в цепочке прототипов висит toString или valueOf - привет, лишние итерации и кривая логика. Object.keys фильтрует их на корню, map добавляет трансформацию без костылей.</p>
<h2>Почему for…in - это мина под нормализацией</h2>
<p dir="auto">for…in перебирает все enumerable свойства, включая те, что притащились из прототипа. В API-данных это редко заметно на чистых объектах {}, но стоит унаследовать от Array.prototype или Object.prototype - и код фейлит. Представь: нормализуешь response.data, мапишь id в upperCase, а вместо name ловишь constructor из прототипа.</p>
<p dir="auto">Проблема вылазит при рефакторе, когда добавляешь полифиллы или расширяешь Object.prototype. hasOwnProperty спасает, но это boilerplate в каждом цикле. Object.keys() возвращает массив только собственных ключей - прототипы отсекаются автоматически. С map() или for…of получаешь чистую итерацию с трансформацией на лету.</p>
<ul>
<li><strong>Enumerable свойства из прототипа</strong>: for…in их подхватывает, keys() - игнорит.</li>
<li><strong>Порядок итерации</strong>: в keys() - как в объекте, в for…in - не гарантирован.</li>
<li><strong>Производительность</strong>: на больших объектах keys() + map быстрее for…in с проверками.</li>
</ul>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>Свойство</th>
<th>for…in</th>
<th>Object.keys() + map</th>
</tr>
</thead>
<tbody>
<tr>
<td>Собственные ключи</td>
<td>Только с hasOwnProperty</td>
<td>Автоматически</td>
</tr>
<tr>
<td>Прототипы</td>
<td>Перебирает</td>
<td>Игнорирует</td>
</tr>
<tr>
<td>Трансформация</td>
<td>Ручная в цикле</td>
<td>Через map()</td>
</tr>
<tr>
<td>Память</td>
<td>Минимальная</td>
<td>+ массив ключей, но оптимизировано</td>
</tr>
</tbody>
</table>
<h2>Нормализация API: реальные грабли с примерами</h2>
<p dir="auto">API возвращает { users: [{id:1, name:‘John’}, {id:2}] }. Нормализуешь в {1: {name:‘John’}, 2: {…}}. for…in по users увидит toString, если массив унаследовал прототип. Результат - лишний ключ в нормализованном объекте, валидация слетает.</p>
<p dir="auto">Object.keys(users).reduce((acc, id) =&gt; { acc[id] = normalizeUser(users[id]); return acc; }, {}). Здесь map заменит reduce для brevity, прототипы не лезут. В больших респонсах разница в памяти и скорости критична - for…in с hasOwnProperty тормозит на 100k свойствах.</p>
<pre><code class="language-javascript">// Плохо: for...in
for(let key in apiData) {
  if (apiData.hasOwnProperty(key)) {
    normalized[key.toUpperCase()] = apiData[key];
  }
}

// Хорошо: keys + map
const normalized = Object.keys(apiData)
  .map(key =&gt; ({ [key.toUpperCase()]: apiData[key] }))
  .reduce((acc, pair) =&gt; ({...acc, ...pair}), {});
</code></pre>
<ul>
<li><strong>Баг #1</strong>: toString в валидации форм - поле ‘toString’ не проходит схему.</li>
<li><strong>Баг #2</strong>: valueOf мешает числовым id при маппинге.</li>
<li><strong>Баг #3</strong>: Расширенный прототип (lodash._) ломает всю цепочку.</li>
</ul>
<h2>Object.entries() как альтернатива map</h2>
<p dir="auto">Entries() дает пары [key, value] сразу - идеально для нормализации без двойного доступа obj[key]. for…in требует ручного obj[key], что на слабых девайсах жрет циклы. map над entries() - функциональный стиль без мутаций.</p>
<p dir="auto">В API с nested объектами entries() + flatMap рвут for…in по читаемости. Прототипы? Забудь - entries() только свои свойства. Плюс, работает с destructuring: for(const [key, val] of Object.entries(obj)).</p>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>Метод</th>
<th>Когда юзать</th>
<th>Минусы</th>
</tr>
</thead>
<tbody>
<tr>
<td>keys() + map</td>
<td>Простая трансформация ключей</td>
<td>Лишний шаг для значений</td>
</tr>
<tr>
<td>entries()</td>
<td>Ключ-значение пары</td>
<td>Больше памяти на массив</td>
</tr>
<tr>
<td>for…of keys()</td>
<td>Иммутабельность</td>
<td>Строго JS2015+</td>
</tr>
</tbody>
</table>
<ul>
<li><strong>Гибкость</strong>: destructuring в map(([, value]) =&gt; transform(value)).</li>
<li><strong>С Map вместо Object</strong>: для динамических ключей из API - size() без keys().length.</li>
<li><strong>Proxy хак</strong>: перехват ownKeys() для валидации на лету.</li>
</ul>
<h2>Когда for…in все же прокатит - и зачем его добивать</h2>
<p dir="auto">Редко, но бывает: дебуггинг полного прототипа или legacy код. hasOwnProperty решает 90% багов, но добавляет шум. В нормализации API это антипаттерн - данные чистые, прототипы не нужны.</p>
<p dir="auto">Переходи на keys()/entries() - рефактор за 5 минут, баги в прошлом. Производительность на больших payload’ах дает +70%. Остальное - итераторы под капотом и Reflect API для edge cases.</p>
<p dir="auto">Дальше копай Reflect.ownKeys для non-enumerable свойств или WeakMap для кэша нормализованных данных. Прототипы останутся в прошлом, код - чистым.</p>
]]></description><link>https://forum.exlends.com/topic/2027/object.keys-map-protiv-for...in-bagi-s-prototipami-pri-normalizacii-api-dannyh</link><generator>RSS for Node</generator><lastBuildDate>Wed, 08 Apr 2026 21:46:07 GMT</lastBuildDate><atom:link href="https://forum.exlends.com/topic/2027.rss" rel="self" type="application/rss+xml"/><pubDate>Wed, 08 Apr 2026 06:35:36 GMT</pubDate><ttl>60</ttl></channel></rss>