Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Як отримати всі ключі та значення об'єкта в JavaScript». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**`Object.keys()`**, **`Object.values()`** та **`Object.entries()`** повертають масиви власних перелічуваних імен властивостей, значень або пар ключ-значення об'єкта. ```javascript const user = { name: "Alice", age: 25 }; Object.keys(user); // ["name", "age"] Object.values(user); // ["Alice", 25] Object.entries(user); // [["name", "Alice"], ["age", 25]] ``` **Ключове:** всі три пропускають властивості прототипу та Symbol-ключі.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**`Object.keys()`**, **`Object.values()`** та **`Object.entries()`** - три статичні методи, які витягують власні перелічувані властивості об'єкта у вигляді масивів, кожен повертає різний зріз цих даних. ## Теорія ### Коротко - Об'єкт як картотека з підписаними ящиками: ключі - це підписи, значення - вміст, entries дають і те, і інше - Всі три методи повертають масиви тільки **власних перелічуваних** властивостей, без прототипного ланцюжка і Symbol-ключів - `Object.keys()` для імен, `Object.values()` для даних, `Object.entries()` для пар - Потрібна ітерація з деструктуризацією? `entries()` + `for...of`. Тільки значення для `map()`? `values()` - Порядок вставки зберігається (ES2015+) ### Швидкий приклад ```javascript const user = { name: "Alice", age: 25, role: "admin" }; Object.keys(user); // ["name", "age", "role"] Object.values(user); // ["Alice", 25, "admin"] Object.entries(user); // [["name", "Alice"], ["age", 25], ["role", "admin"]] // Ітерація з деструктуризацією for (const [key, value] of Object.entries(user)) { console.log(`${key}: ${value}`); // "name: Alice", "age: 25", "role: admin" } ``` Всі три методи знімають знімок стану об'єкта в момент виклику. ### Головна різниця Ці методи бачать тільки **власні перелічувані** властивості. Успадковані з прототипу і Symbol-ключі вони пропускають. `for...in` обходить весь прототипний ланцюжок, тому раніше без `hasOwnProperty()` не обходилось. Ці три методи цієї проблеми не мають. ### Коли що використовувати - **`Object.keys()`** - перевірити наявність властивостей або отримати список імен: `Object.keys(config).includes("timeout")` - **`Object.values()`** - трансформувати або агрегувати дані: `Object.values(prices).reduce((sum, p) => sum + p, 0)` - **`Object.entries()`** - коли потрібні одночасно ключ і значення, або конвертація в `Map`: `new Map(Object.entries(obj))` - **`for...in`** - тільки в старому коді, де явно потрібні успадковані властивості ### Таблиця порівняння | Метод | Повертає | Тільки власні? | Порядок збережено? | |-------|---------|----------------|--------------------| | `Object.keys()` | `string[]` | Так | Так (ES2015+) | | `Object.values()` | `any[]` | Так | Так (ES2015+) | | `Object.entries()` | `[string, any][]` | Так | Так (ES2015+) | | `for...in` | Перебирає ключі | Ні (включає прототип) | Не гарантовано | | **Коли використовувати** | `keys` для імен, `values` для даних, `entries` для пар або `Map`, `for...in` тільки якщо потрібні успадковані | | | ### Як це працює V8 перебирає власні рядкові ключі об'єкта через дескриптори прихованого класу і будує новий масив у порядку вставки (стабільно з ES2015). Symbol-ключі повністю пропускаються. Геттери виконуються під час `Object.values()`, тому результат відображає те, що геттер повернув у момент виклику. Якщо потрібні і Symbol-ключі, використовуй `Reflect.ownKeys(obj)`. ### Типові помилки **Помилка: очікувати Symbol-ключі у результаті** ```javascript const obj = { a: 1, [Symbol("id")]: 42 }; Object.keys(obj); // ["a"] - Symbol зник // Рішення: Reflect.ownKeys(obj); // ["a", Symbol(id)] ``` Symbol-ключі невидимі для всіх трьох методів. Це часта точка здивування. **Помилка: `for...in` без `hasOwnProperty` на звичайних об'єктах** ```javascript function Base() {} Base.prototype.inherited = "oops"; const obj = new Base(); obj.own = "mine"; for (const key in obj) { console.log(key); // "own", "inherited" - прototипні властивості потрапляють у цикл } // Ці три методи такої проблеми не мають: Object.keys(obj); // ["own"] ``` **Помилка: мутувати об'єкт під час ітерації** ```javascript const obj = { a: 1, b: 2 }; for (const [k, v] of Object.entries(obj)) { obj[`copy_${k}`] = v; // додаємо властивості під час циклу } // Цикл бачить тільки "a" і "b" - entries() зробив знімок на початку // Але об'єкт отримає зайві ключі, що може призвести до непередбачуваних помилок // Рішення: спочатку клонуй for (const [k, v] of Object.entries({ ...obj })) { obj[`copy_${k}`] = v; } ``` ### Де зустрічається в реальному коді - **React** - `Object.entries(props)` для рендерингу динамічних `<option>` в select - **Redux** - `Object.keys(state.entities)` для отримання масиву ID в селекторах - **Express** - `Object.values(req.headers)` для логування всіх заголовків - **Node.js** - `Object.entries(process.env)` для безпечної ітерації змінних середовища - **Lodash** - використовує `entries()` всередині `.toPairs()` та `.fromPairs()` ### Питання на співбесіді **Q:** Що поверне `Object.keys({})`? **A:** Порожній масив `[]`. Немає властивостей - немає ключів. **Q:** Яка різниця між `Object.keys()` і `for...in`? **A:** `Object.keys()` повертає тільки власні перелічувані рядкові ключі в порядку вставки. `for...in` обходить весь прототипний ланцюжок і не гарантує порядок. У сучасному коді краще `Object.keys()`. **Q:** Чи виконує `Object.values()` геттер-функції? **A:** Так. Якщо властивість оголошена як геттер, `Object.values()` викликає його і включає результат у масив. **Q:** Як отримати всі ключі, включаючи Symbol? **A:** `Reflect.ownKeys(obj)`. Повертає і рядкові, і Symbol-ключі в одному масиві. **Q:** Якщо додати властивість після виклику `Object.entries()`, чи побачить її цикл? **A:** Ні. `Object.entries()` робить знімок у момент виклику. Властивості, додані після, в ітерацію не потраплять. ## Приклади ### Ключі, значення та пари для config-об'єкта ```javascript const config = { host: "localhost", port: 3000, debug: true }; // Перевірити наявність ключа (тільки власна властивість) Object.keys(config).includes("port"); // true // З'єднати значення в рядок Object.values(config).join(", "); // "localhost, 3000, true" // Побудувати query string з entries const query = Object.entries(config) .map(([key, value]) => `${key}=${value}`) .join("&"); // "host=localhost&port=3000&debug=true" ``` `Object.keys()` підходить для перевірки наявності без торкання значень. `Object.entries()` - коли потрібні обидві сторони одразу. ### Конвертація відповіді API в Map ```javascript // API повертає плаский об'єкт з динамічними ключами const permissions = { read: true, write: false, delete: true }; // Конвертуємо в Map для швидкого пошуку за ключем const permMap = new Map(Object.entries(permissions)); permMap.get("write"); // false permMap.has("admin"); // false // Фільтруємо тільки активні права const active = Object.entries(permissions) .filter(([, value]) => value === true) .map(([key]) => key); // ["read", "delete"] ``` Я тягнусь до цього патерну кожного разу, коли API повертає об'єкт з правами або налаштуваннями, де ключі наперед невідомі. `Map` зручніший для динамічних пошуків, ніж повторні перевірки через `in`.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.