Skip to main content

Різниця між оператором in та методом hasOwnProperty() у JavaScript

Оператор in перевіряє, чи існує властивість десь у ланцюгу прототипів об'єкта, включаючи успадковані. hasOwnProperty() дивиться тільки на власні властивості об'єкта і ланцюг ігнорує.

Теорія

Коротко

  • in - як запитати "чи є в цьому будинку або в будь-якого з його предків гараж?" hasOwnProperty() питає "чи є гараж саме в цьому будинку?"
  • Головна різниця: in обходить весь ланцюг прототипів, hasOwnProperty() зупиняється на самому об'єкті
  • Обидва бачать non-enumerable власні властивості. Фільтрацію за enumerability робить Object.keys(), а не hasOwnProperty()
  • in - для повної перевірки існування, включаючи успадковані. hasOwnProperty() - тільки для прямого володіння

Швидкий приклад

javascript
const parent = { skill: 'coding' }; const child = Object.create(parent); child.name = 'Alice'; console.log('name' in child); // true (власна властивість) console.log('skill' in child); // true (успадковано від parent) console.log(child.hasOwnProperty('name')); // true console.log(child.hasOwnProperty('skill')); // false (є на parent, не на child)

in знайшов обидві властивості. hasOwnProperty() повернув false для skill, бо вона належить parent, а не child напряму.

Ключова різниця

in запускає внутрішній метод [[HasProperty]], який проходить по [[Prototype]]-посиланнях вгору, доки не знайде ключ або не дійде до null. hasOwnProperty() йде напряму до таблиці властивостей об'єкта і ланцюг не чіпає. Обидва методи бачать non-enumerable власні властивості. Фільтрацію за ознакою enumerable робить Object.keys(), а не hasOwnProperty().

Коли що використовувати

  • Перевірка перед доступом до властивості (захист від помилки): in
  • Визначення наявності браузерних API через прототипи: in
  • Фільтрація прямих ключів усередині циклу for...in: hasOwnProperty()
  • Серіалізація тільки тих властивостей, що визначив користувач, без значень з прототипу: hasOwnProperty()
  • Захист від prototype pollution у коді бібліотек: hasOwnProperty()

Таблиця порівняння

ХарактеристикаinhasOwnProperty()
Перевіряє ланцюг прототипівТакНі (тільки власні)
Non-enumerable власні властивостіПовертає trueПовертає true
Об'єкти без прототипу (Object.create(null))ПрацюєTypeError, якщо викликати напряму
ПродуктивністьПовільнішеШвидше
Типове застосуванняБезпечний доступ, feature detectionФільтрація в for...in, серіалізація

Як це працює всередині

in викликає внутрішній метод ECMAScript [[HasProperty]], який іде по ланцюгу [[Prototype]] до null. hasOwnProperty() звертається прямо до таблиці властивостей об'єкта. Тому hasOwnProperty() у щільних циклах приблизно у 2-3 рази швидший.

Типові помилки

Помилка: for...in без фільтрації власних властивостей

javascript
function User() {} User.prototype.greet = function() {}; const user = new User(); user.name = 'Alice'; for (let key in user) { console.log(key); // 'name', потім 'greet' (успадкований!) }

for...in проходить по всіх enumerable властивостях, включаючи успадковані. Виправлення:

javascript
for (let key in user) { if (user.hasOwnProperty(key)) { console.log(key); // тільки 'name' } }

Або Object.keys(user), який одразу повертає тільки власні enumerable ключі.

Помилка: виклик hasOwnProperty на об'єкті без прототипу

javascript
const config = Object.create(null); // без прототипу config.port = 3000; config.hasOwnProperty('port'); // TypeError: config.hasOwnProperty is not a function

Об'єкти з Object.create(null) не мають прототипу, тому hasOwnProperty у них немає. Виправлення:

javascript
Object.prototype.hasOwnProperty.call(config, 'port'); // true // Або сучасний варіант: Object.hasOwn(config, 'port'); // true (ES2022)

Я натрапляв на це в Node.js-конфіг-лоадерах, де null-прототип об'єкти використовуються для захисту від prototype pollution. Object.hasOwn вирішує проблему чисто.

Помилка: думати, що hasOwnProperty не бачить non-enumerable властивостей

javascript
const obj = {}; Object.defineProperty(obj, 'hidden', { value: 99, enumerable: false }); console.log('hidden' in obj); // true console.log(obj.hasOwnProperty('hidden')); // true (а не false!) console.log(Object.keys(obj)); // [] <- ось хто пропускає non-enumerable

hasOwnProperty() не фільтрує за enumerable. Він повертає true для будь-якої власної властивості. Object.keys() - той, хто вимагає enumerable: true.

Де зустрічається в реальних проектах

  • React: for...in + hasOwnProperty у ReactElementValidator для перевірки propTypes (React 18)
  • Express: req.hasOwnProperty('user') для захисту від prototype leaks у middleware
  • Lodash: _.has(object, path) використовує логіку на кшталт in для безпечного доступу вглиб
  • Node.js: null-прототип об'єкти у парсерах конфігурацій разом з Object.hasOwn

Питання на співбесіді

Q: Що повернеться при використанні in на об'єкті, створеному через Object.create(null)?
A: Для власних властивостей поверне true, але нічого успадкованого не знайде, бо ланцюга прототипів немає. 'toString' in Object.create(null) поверне false.

Q: Що таке Object.hasOwn() і чим відрізняється від hasOwnProperty()?
A: Object.hasOwn(obj, key) доданий в ES2022. Робить те саме, що hasOwnProperty(), але коректно працює з null-прототип об'єктами. У новому коді краще використовувати саме його.

Q: Як безпечно обійти тільки власні ключі?
A: Object.keys(obj) для власних enumerable ключів або for...in з фільтром hasOwnProperty(). Object.keys виглядає чистіше.

Q: Чи спрацьовує has trap у Proxy при перевірці через in?
A: Так. in викликає has trap Proxy, тобто можна перехопити цю перевірку. hasOwnProperty() обходить trap і йде напряму до цільового об'єкта. Важливо при метапрограмуванні, прописано в специфікації ES2022.

Приклади

Перевірка власних і успадкованих властивостей у ланцюгу класів

javascript
function Animal(name) { this.name = name; } Animal.prototype.type = 'animal'; const dog = new Animal('Rex'); console.log('name' in dog); // true (власна) console.log('type' in dog); // true (успадкована з Animal.prototype) console.log(dog.hasOwnProperty('name')); // true console.log(dog.hasOwnProperty('type')); // false

type живе на Animal.prototype, а не на dog. in знаходить її, hasOwnProperty() - ні. Найчіткіша демонстрація різниці.

Серіалізація тільки користувацьких налаштувань

javascript
const defaults = { theme: 'light', lang: 'en' }; const userConfig = Object.create(defaults); userConfig.lang = 'uk'; userConfig.fontSize = 16; // Зберігаємо тільки те, що встановив користувач const toSave = {}; for (let key in userConfig) { if (userConfig.hasOwnProperty(key)) { toSave[key] = userConfig[key]; } } console.log(toSave); // { lang: 'uk', fontSize: 16 } // 'theme' не потрапляє, бо він на defaults, а не на userConfig

Патерн, який часто зустрічається у системах конфігурацій. Без перевірки hasOwnProperty у результат потраплять значення за замовчуванням, яких користувач не торкався.

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

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

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

Дочитали статтю?