Різниця між оператором in та методом hasOwnProperty() у JavaScript
Оператор in перевіряє, чи існує властивість десь у ланцюгу прототипів об'єкта, включаючи успадковані. hasOwnProperty() дивиться тільки на власні властивості об'єкта і ланцюг ігнорує.
Теорія
Коротко
in- як запитати "чи є в цьому будинку або в будь-якого з його предків гараж?"hasOwnProperty()питає "чи є гараж саме в цьому будинку?"- Головна різниця:
inобходить весь ланцюг прототипів,hasOwnProperty()зупиняється на самому об'єкті - Обидва бачать non-enumerable власні властивості. Фільтрацію за enumerability робить
Object.keys(), а неhasOwnProperty() in- для повної перевірки існування, включаючи успадковані.hasOwnProperty()- тільки для прямого володіння
Швидкий приклад
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()
Таблиця порівняння
| Характеристика | in | hasOwnProperty() |
|---|---|---|
| Перевіряє ланцюг прототипів | Так | Ні (тільки власні) |
| Non-enumerable власні властивості | Повертає true | Повертає true |
Об'єкти без прототипу (Object.create(null)) | Працює | TypeError, якщо викликати напряму |
| Продуктивність | Повільніше | Швидше |
| Типове застосування | Безпечний доступ, feature detection | Фільтрація в for...in, серіалізація |
Як це працює всередині
in викликає внутрішній метод ECMAScript [[HasProperty]], який іде по ланцюгу [[Prototype]] до null. hasOwnProperty() звертається прямо до таблиці властивостей об'єкта. Тому hasOwnProperty() у щільних циклах приблизно у 2-3 рази швидший.
Типові помилки
Помилка: for...in без фільтрації власних властивостей
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 властивостях, включаючи успадковані. Виправлення:
for (let key in user) {
if (user.hasOwnProperty(key)) {
console.log(key); // тільки 'name'
}
}Або Object.keys(user), який одразу повертає тільки власні enumerable ключі.
Помилка: виклик hasOwnProperty на об'єкті без прототипу
const config = Object.create(null); // без прототипу
config.port = 3000;
config.hasOwnProperty('port'); // TypeError: config.hasOwnProperty is not a functionОб'єкти з Object.create(null) не мають прототипу, тому hasOwnProperty у них немає. Виправлення:
Object.prototype.hasOwnProperty.call(config, 'port'); // true
// Або сучасний варіант:
Object.hasOwn(config, 'port'); // true (ES2022)Я натрапляв на це в Node.js-конфіг-лоадерах, де null-прототип об'єкти використовуються для захисту від prototype pollution. Object.hasOwn вирішує проблему чисто.
Помилка: думати, що hasOwnProperty не бачить non-enumerable властивостей
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-enumerablehasOwnProperty() не фільтрує за 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.
Приклади
Перевірка власних і успадкованих властивостей у ланцюгу класів
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')); // falsetype живе на Animal.prototype, а не на dog. in знаходить її, hasOwnProperty() - ні. Найчіткіша демонстрація різниці.
Серіалізація тільки користувацьких налаштувань
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 у результат потраплять значення за замовчуванням, яких користувач не торкався.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.