Пошук властивостей у ланцюгу прототипів
Коротка відповідь
Коли ти звертаєшся до властивості об'єкта, наприклад:
obj.propJavaScript шукає цю властивість у такому порядку:
- Спочатку в самому об'єкті (
obj); - Якщо не знайдено - в його прототипі (
obj.__proto__абоObject.getPrototypeOf(obj)); - Потім - у прототипі прототипу, і так далі;
- Поки не дійде до
null(вершини ланцюга).
Якщо властивість не знайдено ніде - результат undefined.
Ця послідовність і називається ланцюг прототипів (prototype chain).
Приклад: поетапний пошук
const animal = { eats: true };
const dog = Object.create(animal);
dog.barks = true;
console.log(dog.barks); // знайдено в dog → true
console.log(dog.eats); // немає в dog → шукаємо в animal → true
console.log(dog.toString); // немає в animal → шукаємо в Object.prototype → [Function]
console.log(dog.fly); // немає ніде → undefinedЛанцюг:
dog → animal → Object.prototype → nullВізуально
dog {
barks: true
}
↑
|
animal {
eats: true
}
↑
|
Object.prototype {
toString(), hasOwnProperty(), ...
}
↑
|
nullАлгоритм під капотом (покроково)
Коли виконується obj.prop:
- JS перевіряє:
чи є властивість
propу самому об'єкті (власна властивість)? → якщо так - повернути її значення. → якщо ні - йдемо далі. - JS отримує прототип об'єкта:
const parent = Object.getPrototypeOf(obj);- Перевіряє, чи є
propуparent. - Повторює кроки, поки:
- властивість знайдена
- або прототип дорівнює
null
Все це робиться динамічно, без копіювання властивостей.
При записі властивості (obj.prop = value)
Тут механізм трохи інший:
- Якщо властивість існує в самому об'єкті, → вона просто перезаписується.
- Якщо ні - створюється нова властивість в
obj(не в прототипі). - Прототип взагалі не зачіпається.
const animal = { eats: true };
const dog = Object.create(animal);
dog.eats = false; // створює нову властивість у dog
console.log(dog.eats); // false (власна)
console.log(animal.eats); // true (у прототипі, не змінилося)При читанні і записі - різні ланцюги!
| Операція | Де шукає | Що робить |
|---|---|---|
obj.prop (читання) | Шукає в obj, потім по ланцюгу вгору | Повертає перше знайдене |
obj.prop = value (запис) | Завжди пише в obj, якщо writable: true | Не змінює прототип |
delete obj.prop | Видаляє тільки в obj | Не впливає на прототип |
Якщо в ланцюгу є геттер або сеттер
Якщо на будь-якому рівні знайдено геттер (get prop()), він виконується.
Якщо знайдено сеттер, він виконується при записі.
const animal = {
get info() {
return 'Animal info';
}
};
const dog = Object.create(animal);
console.log(dog.info); // "Animal info" - викликано геттер у прототипаТобто прототипи можуть містити "реактивні" властивості, а не тільки дані.
Приклад з "глибоким" ланцюгом
const a = { level: 'A' };
const b = Object.create(a);
const c = Object.create(b);
const d = Object.create(c);
console.log(d.level); // "A"Пошук проходить 4 об'єкти, перш ніж знайде level в a.
Оптимізація рушієм
JS-рушії (V8, SpiderMonkey та ін.) оптимізують ланцюг прототипів:
- створюють внутрішні "shape"-карти (структури об'єктів);
- кешують знайдені властивості;
- при зміні прототипу або властивостей кеш скидається.
Тому змінювати прототип під час виконання (Object.setPrototypeOf) -
повільна операція, і її краще уникати.
Приклад: що буде, якщо прототип - null
const pure = Object.create(null);
pure.key = 'value';
console.log(pure.toString); // undefined (немає Object.prototype)Тут ланцюг обривається одразу:
pure → nullПошук припиняється миттєво, якщо прототип дорівнює null.
ПІДСУМОК
| Етап | Що відбувається |
|---|---|
| 1 | JS шукає властивість у самому об'єкті |
| 2 | Якщо не знайдено - йде в [[Prototype]] |
| 3 | Продовжує вгору по ланцюгу |
| 4 | Якщо дійшов до null - повертає undefined |
| Примітка | При записі створюється нова властивість в об'єкті, не в прототипі |
| Примітка | Геттери/сеттери в ланцюгу теж враховуються |
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.