Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Чистий об'єкт без prototype». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**Чистий об'єкт, тобто об'єкт зовсім без прототипа, створюють викликом `Object.create(null)`: у нього `Object.getPrototypeOf(obj) === null`, і він не успадковує жодного вбудованого методу на кшталт `toString` чи `hasOwnProperty`.** Це робить його ідеальним сховищем даних, словником, де жоден ключ не конфліктує з вбудованими іменами, зокрема з `__proto__`. ```javascript const dict = Object.create(null); dict.toString = 'value'; // просто звичайний ключ console.log(Object.getPrototypeOf(dict)); // null ``` **Ключове:** `Object.create(null)` дає об'єкт без ланцюжка прототипів, а платою за це є відсутність вбудованих методів.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**Чистий об'єкт (його ще називають object dictionary) - це об'єкт без прототипа, тобто без `Object.prototype` і всіх його вбудованих методів на кшталт `toString` чи `hasOwnProperty`.** Такий об'єкт чудово підходить для чистих сховищ даних, словників і мап, де треба бути впевненим, що жодна вбудована властивість не перетнеться з вашими ключами. ## Теорія ### TL;DR - Чистий об'єкт створюється викликом `Object.create(null)`. - У нього `Object.getPrototypeOf(obj)` дорівнює `null`, ланцюжка прототипів немає взагалі. - Він не має ні `toString`, ні `hasOwnProperty`, ні спеціальної поведінки `__proto__`. - Тому ключі `toString` або `__proto__` стають звичайними ключами і нічого не ламають. - Перевіряти наявність властивості треба через `Object.prototype.hasOwnProperty.call(obj, key)` або `Object.hasOwn(obj, key)`. - Сучасна альтернатива для словників, це `Map`. ### Швидкий приклад Найпростіший і правильний спосіб: ```javascript const obj = Object.create(null); ``` Це створює об'єкт без прототипа: `obj.__proto__` дорівнює `undefined`, а `Object.getPrototypeOf(obj)` дорівнює `null`. Перевіримо: ```javascript const obj = Object.create(null); console.log(Object.getPrototypeOf(obj)); // null console.log('__proto__' in obj); // false console.log(typeof obj.toString); // "undefined" ``` У такого об'єкта немає ланцюжка успадкування: він не успадковує навіть від `Object.prototype`. ### Чому це корисно Звичайні об'єкти успадковують купу вбудованих методів: ```javascript const normal = {}; console.log(Object.getPrototypeOf(normal) === Object.prototype); // true // Ці методи доступні console.log(normal.hasOwnProperty); // [Function] console.log(normal.toString); // [Function] ``` Це може бути небезпечно, якщо ключі перетинаються з цими іменами: ```javascript const map = {}; map.toString = 'value'; // може зламати код, якщо хтось викличе map.toString() ``` Чистий об'єкт знімає цю проблему: ```javascript const map = Object.create(null); map.toString = 'value'; // просто ключ console.log(map.toString); // "value" ``` ### Приклад використання: словники й мапи ```javascript const dict = Object.create(null); dict.apple = 1; dict.banana = 2; dict['__proto__'] = 3; // безпечно, не впливає на прототип console.log(dict.apple); // 1 console.log(dict['__proto__']); // 3 ``` У звичайних об'єктах `__proto__` має особливу поведінку (це аксесор з `Object.prototype`), а тут це звичайна властивість. Саме тому чисті об'єкти часто використовують для кешів і для розбору даних, що прийшли ззовні: вони природно захищені від prototype pollution. ### Недолік: немає вбудованих методів ```javascript const obj = Object.create(null); obj.name = 'Alice'; console.log(obj.hasOwnProperty('name')); // TypeError: obj.hasOwnProperty is not a function ``` Якщо треба перевірити наявність властивості, скористайтеся безпечним способом: ```javascript console.log(Object.prototype.hasOwnProperty.call(obj, 'name')); // true console.log(Object.hasOwn(obj, 'name')); // true, сучасний варіант ``` Те саме стосується `toString()`: такий об'єкт не можна просто конкатенувати з рядком, а `console.log` у деяких середовищах показує його як `[Object: null prototype]`. ### Альтернатива: використовувати `Map` Сучасний спосіб зберігати словники: ```javascript const map = new Map(); map.set('apple', 1); map.set('__proto__', 2); console.log(map.get('__proto__')); // 2 ``` `Map` розв'язує ту саму задачу, але дозволяє будь-які ключі, зокрема об'єкти, зберігає порядок вставки і має властивість `size`. ### Підсумок | Спосіб | Прототип | Доступ до вбудованих методів | Підходить для словників | | --- | --- | --- | --- | | `{}` | `Object.prototype` | є (`toString`, `hasOwnProperty`) | ризиковано | | `Object.create(null)` | `null` | немає | так | | `new Map()` | не звичайний об'єкт | методи `set`, `get`, `has` | так, сучасний варіант | > Щоб створити чистий об'єкт без `Object.prototype`, використовуйте `const obj = Object.create(null)`: він не успадковує нічого і чудово підходить для безпечних словників. ### Типові помилки - **Викликати `obj.hasOwnProperty(key)` на чистому об'єкті.** Отримаєте `TypeError`, бо методу просто немає. Використовуйте `Object.hasOwn(obj, key)`. - **Плутати `Object.create(null)` з `{}`.** Другий варіант успадковує від `Object.prototype` з усіма наслідками. - **Передавати чистий об'єкт у код, який очікує звичайний.** Бібліотека, яка всередині викликає `value.toString()`, впаде. - **Думати, що `JSON.stringify` не працює.** Він працює нормально, проблеми виникають саме з неявним приведенням до рядка. - **Використовувати чистий об'єкт там, де краще підійде `Map`.** Якщо ключі не рядки або потрібен розмір і порядок, беріть `Map`. - **Забувати, що об'єкт з `JSON.parse` звичайний.** Розбір зовнішнього JSON дає об'єкт з прототипом, тому перевіряйте ключ `__proto__` перед злиттям.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.