Пошук уроків, статей та іншого контенту
Застосуєте Object.keys, Object.getOwnPropertyNames і Reflect для розрізнення власних та успадкованих властивостей.
Об’єкт у JavaScript може отримувати властивості з двох джерел:
власні властивості — записані безпосередньо в об’єкті;
успадковані властивості — доступні через ланцюжок прототипів.
const animal = {
legs: 4,
};
const dog = Object.create(animal);
dog.name = "Rex";
console.log(dog.name); // Rex — власна властивість
console.log(dog.legs); // 4 — успадкована властивістьВластивість legs доступна через dog, але фізично вона зберігається в об’єкті animal, який є прототипом dog.
console.log(Object.hasOwn(dog, "name")); // true
console.log(Object.hasOwn(dog, "legs")); // falseМетод Object.hasOwn() перевіряє, чи належить властивість самому об’єкту, а не його прототипу.
Якщо властивість не знайдена безпосередньо в об’єкті, JavaScript шукає її в прототипі. Якщо властивості немає і там, пошук продовжується далі.
const creature = {
canMove: true,
};
const animal = Object.create(creature);
animal.legs = 4;
const dog = Object.create(animal);
dog.name = "Rex";
console.log(dog.name); // Rex — власна властивість dog
console.log(dog.legs); // 4 — властивість animal
console.log(dog.canMove); // true — властивість creatureЛанцюжок у цьому прикладі має такий вигляд:
dog → animal → creature → Object.prototype → nullУспадковані властивості можна читати, але це не означає, що вони є власними властивостями об’єкта.
Object.keys()Метод Object.keys(object) повертає масив імен власних перелічуваних властивостей об’єкта.
const prototype = {
inherited: "успадкована властивість",
};
const object = Object.create(prototype);
object.visible = "власна властивість";
Object.defineProperty(object, "hidden", {
value: "неперелічувана властивість",
enumerable: false,
});
console.log(Object.keys(object));
// ["visible"]Object.keys():
повертає лише власні властивості;
пропускає успадковані властивості;
повертає лише властивості з enumerable: true;
не повертає властивості-символи.
Більшість властивостей, створених звичайним присвоєнням, є перелічуваними:
const user = {};
user.name = "Anna";
console.log(Object.getOwnPropertyDescriptor(user, "name"));
// {
// value: "Anna",
// writable: true,
// enumerable: true,
// configurable: true
// }Властивість, створена через Object.defineProperty() без явного enumerable: true, за замовчуванням є неперелічуваною.
const user = {};
Object.defineProperty(user, "id", {
value: 42,
});
console.log(Object.keys(user)); // []
console.log(user.id); // 42Властивість існує і доступна через крапку, але Object.keys() її не показує.
Object.getOwnPropertyNames()Метод Object.getOwnPropertyNames(object) повертає імена всіх власних властивостей, зокрема неперелічуваних.
const object = {
visible: "видима властивість",
};
Object.defineProperty(object, "hidden", {
value: "прихована від переліку властивість",
enumerable: false,
});
console.log(Object.getOwnPropertyNames(object));
// ["visible", "hidden"]На відміну від Object.keys() цей метод:
бачить власні перелічувані властивості;
бачить власні неперелічувані властивості;
не бачить успадковані властивості;
не повертає властивості-символи.
length у масивівКорисний приклад неперелічуваної властивості — length масиву:
const numbers = [10, 20, 30];
console.log(Object.keys(numbers));
// ["0", "1", "2"]
console.log(Object.getOwnPropertyNames(numbers));
// ["0", "1", "2", "length"]Індекси масиву є власними перелічуваними властивостями, а length — власною неперелічуваною властивістю.
Символи можуть бути ключами властивостей об’єкта:
const id = Symbol("id");
const user = {
name: "Anna",
[id]: 123,
};
console.log(Object.keys(user));
// ["name"]
console.log(Object.getOwnPropertyNames(user));
// ["name"]Ні Object.keys(), ні Object.getOwnPropertyNames() не повертають властивості-символи.
Щоб отримати всі власні ключі, включно із символами, використовують Reflect.ownKeys().
Reflect.ownKeys()Метод Reflect.ownKeys(object) повертає всі власні ключі об’єкта:
рядкові ключі, включно з неперелічуваними;
ключі-символи;
але не успадковані ключі.
const secret = Symbol("secret");
const object = {
name: "Anna",
[secret]: "службове значення",
};
Object.defineProperty(object, "id", {
value: 42,
enumerable: false,
});
console.log(Reflect.ownKeys(object));
// ["name", "id", Symbol(secret)]Це найповніший стандартний спосіб отримати ключі безпосередньо з об’єкта.
| Метод | Власні | Успадковані | Неперелічувані | Символи | |---|---:|---:|---:|---:| | Object.keys() | так | ні | ні | ні | | Object.getOwnPropertyNames() | так | ні | так | ні | | Reflect.ownKeys() | так | ні | так | так |
Object.hasOwn()Сучасний і безпечний спосіб перевірити власність:
const prototype = {
role: "user",
};
const account = Object.create(prototype);
account.name = "Anna";
console.log(Object.hasOwn(account, "name")); // true
console.log(Object.hasOwn(account, "role")); // falseМетод працює навіть для об’єктів без прототипу або для об’єктів, які мають власну властивість із назвою hasOwnProperty.
const object = Object.create(null);
object.hasOwnProperty = "це звичайне значення";
object.name = "Anna";
console.log(Object.hasOwn(object, "name")); // truehasOwnProperty()Метод hasOwnProperty() також може перевірити власність властивості:
const user = {
name: "Anna",
};
console.log(user.hasOwnProperty("name")); // trueОднак не варто безпосередньо покладатися на нього:
const object = {
hasOwnProperty: () => false,
name: "Anna",
};
console.log(object.hasOwnProperty("name")); // false, результат підміненоБезпечніший варіант для старого коду:
console.log(
Object.prototype.hasOwnProperty.call(object, "name")
); // trueАле в сучасному JavaScript зазвичай краще використовувати Object.hasOwn().
Цикл for...in перебирає перелічувані властивості не лише самого об’єкта, а й його прототипів.
const prototype = {
inherited: "успадкована",
};
const object = Object.create(prototype);
object.own = "власна";
for (const key in object) {
console.log(key);
}
// own
// inheritedЯкщо потрібні лише власні властивості, використовуйте перевірку:
for (const key in object) {
if (Object.hasOwn(object, key)) {
console.log(key);
}
}
// ownАбо спочатку отримайте ключі через Object.keys():
for (const key of Object.keys(object)) {
console.log(key);
}
// ownObject.keys() часто є зрозумілішим варіантом, якщо не потрібно спеціально обходити прототипи.
У наступному прикладі продемонстровано різницю між власними, успадкованими, неперелічуваними властивостями та властивостями-символами.
const type = Symbol("type");
const prototype = {
inherited: "успадкована властивість",
};
const object = Object.create(prototype);
object.name = "Документ";
object[type] = "file";
Object.defineProperty(object, "id", {
value: 101,
enumerable: false,
});
console.log("Власні перелічувані ключі:");
console.log(Object.keys(object));
// ["name"]
console.log("Усі власні рядкові ключі:");
console.log(Object.getOwnPropertyNames(object));
// ["name", "id"]
console.log("Усі власні ключі:");
console.log(Reflect.ownKeys(object));
// ["name", "id", Symbol(type)]
console.log("Успадкована властивість:");
console.log(object.inherited);
// "успадкована властивість"
console.log("Перевірка власності:");
console.log(Object.hasOwn(object, "name")); // true
console.log(Object.hasOwn(object, "id")); // true
console.log(Object.hasOwn(object, "inherited")); // false
console.log("Перебір for...in:");
for (const key in object) {
console.log(key);
}
// name
// inheritedЗверніть увагу: властивість id доступна напряму, але не потрапляє в Object.keys() і for...in, тому що вона неперелічувана. Властивість type не потрапляє в ці методи, тому що її ключем є символ.
inОператор in перевіряє, чи існує властивість у самому об’єкті або в його прототипах.
const prototype = {
inherited: true,
};
const object = Object.create(prototype);
object.own = true;
console.log("own" in object); // true
console.log("inherited" in object); // true
console.log("missing" in object); // falseТому оператор in не підходить, якщо потрібно перевірити саме власність.
console.log(Object.hasOwn(object, "own")); // true
console.log(Object.hasOwn(object, "inherited")); // falseРізниця:
key in object — властивість є в об’єкті або його прототипі;
Object.hasOwn(object, key) — властивість записана безпосередньо в об’єкті.
Екземпляри класів також можуть мати власні та успадковані властивості.
class User {
constructor(name) {
this.name = name;
}
greet() {
return `Привіт, ${this.name}!`;
}
}
const user = new User("Anna");
console.log(Object.keys(user));
// ["name"]
console.log(Object.getOwnPropertyNames(user));
// ["name"]
console.log(Object.hasOwn(user, "name"));
// true
console.log(Object.hasOwn(user, "greet"));
// false
console.log("greet" in user);
// truename створюється в конструкторі безпосередньо в екземплярі, тому це власна властивість.
Метод greet оголошений у класі та розміщується в прототипі User.prototype, тому він успадкований для екземпляра user.
console.log(Object.hasOwn(User.prototype, "greet")); // true
console.log(Object.hasOwn(user, "greet")); // falseЗвичайні об’єкти успадковують властивості від Object.prototype.
const object = {
name: "Документ",
};
console.log("toString" in object); // true
console.log(Object.hasOwn(object, "toString")); // falseМетод toString доступний, але він не є власною властивістю object.
console.log(Object.keys(object));
// ["name"]
console.log(Object.getOwnPropertyNames(object));
// ["name"]
console.log(Reflect.ownKeys(object));
// ["name"]Ці методи не піднімаються по ланцюжку прототипів. Вони аналізують лише властивості конкретного об’єкта.
Якщо потрібно зібрати властивості об’єкта разом із властивостями його прототипів, ланцюжок потрібно пройти вручну.
const base = {
baseProperty: "base",
};
const middle = Object.create(base);
middle.middleProperty = "middle";
const object = Object.create(middle);
object.ownProperty = "own";
const allKeys = new Set();
let current = object;
while (current !== null) {
for (const key of Reflect.ownKeys(current)) {
allKeys.add(key);
}
current = Object.getPrototypeOf(current);
}
console.log([...allKeys]);
// ["ownProperty", "middleProperty", "baseProperty", ...]У цьому прикладі:
Reflect.ownKeys() отримує всі ключі поточного об’єкта;
Object.getPrototypeOf() повертає його прототип;
цикл продовжується, доки прототип не стане null;
Set видаляє дублікати ключів.
Якщо одна й та сама властивість існує в кількох рівнях прототипного ланцюжка, першою буде властивість найближчого до об’єкта рівня.
const prototype = {
role: "admin",
};
const user = Object.create(prototype);
console.log(user.role); // "admin"Той факт, що властивість можна прочитати, не означає, що вона є власною.
console.log(Object.hasOwn(user, "role")); // falsein для перевірки власностіconst prototype = {
active: true,
};
const user = Object.create(prototype);
console.log("active" in user); // truein враховує прототипи. Для перевірки власної властивості використовуйте:
console.log(Object.hasOwn(user, "active")); // falseObject.keys() поверне всі властивостіconst object = {};
Object.defineProperty(object, "id", {
value: 1,
enumerable: false,
});
console.log(Object.keys(object)); // []Для власних неперелічуваних рядкових властивостей використовуйте:
console.log(Object.getOwnPropertyNames(object)); // ["id"]const key = Symbol("key");
const object = {
[key]: "value",
};
console.log(Object.keys(object)); // []Щоб отримати і рядкові, і символьні ключі:
console.log(Reflect.ownKeys(object)); // [Symbol(key)]hasOwnProperty()Власна властивість може перезаписати метод hasOwnProperty, а об’єкт може взагалі не мати прототипу.
const object = Object.create(null);
object.name = "Anna";
// object.hasOwnProperty("name") спричинить помилку
console.log(Object.hasOwn(object, "name")); // trueУ сучасному JavaScript використовуйте Object.hasOwn().
Власна властивість зберігається безпосередньо в об’єкті.
Успадкована властивість знаходиться в одному з прототипів об’єкта.
Object.keys() повертає власні перелічувані рядкові ключі.
Object.getOwnPropertyNames() повертає всі власні рядкові ключі, включно з неперелічуваними.
Reflect.ownKeys() повертає всі власні ключі, включно з неперелічуваними та символьними.
Object.hasOwn(object, key) перевіряє саме власність властивості.
Оператор in перевіряє об’єкт разом із його прототипами.
for...in перебирає перелічувані властивості об’єкта та його прототипів.
Методи класу зазвичай є властивостями прототипу екземпляра, а поля, створені в конструкторі, — його власними властивостями.