Skip to main content

Чистий об'єкт без prototype

Чистий об'єкт (його ще називають 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__ перед злиттям.

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.