Прототипы, наследование
В программировании часто возникает необходимость что-то расширить.
Например у нас есть объект user
со своими свойствами и методами, и мы хотим создать объекты admin
и guest
как его
слегка измененные варианты. Мы хотели бы повторно использовать то, что есть у объекта user
, не копировать его методы,
а создать новый объект на его основе.
Прототипное наследование — это возможность языка, которая помогает в этом.
[[Prototype]]
В JavaScript объекты имеют специальное скрытое свойство [[Prototype]]
, которое либо равно null
, либо ссылается на
другой объект. Этот объект называется «прототип».
Когда мы хотим прочитать свойство из object
, а оно отсутствует, JavaScript автоматически берет его из прототипа.
Свойство [[Prototype]]
является внутренним и скрытым, но есть много способов задать его.
Один из способов — использование __proto__
:
let animal = {
eats: true
}
let rabbit = {
jumps: true
}
rabbit.__proto__ = animal; // (*)
// теперь мы можем найти оба свойства в rabbit:
console.log(rabbit.eats); // -> true (**)
console.log(rabbit.jumps); // -> true
Здесь строка (*)
устанавливает animal
как прототип для rabbit
.
Затем, когда console.log
пытается прочитать свойство rabbit.eats
(**)
, его нет в rabbit
, поэтому JavaScript
следует по ссылке [[Prototype]]
и находит его в animal
.
Так что если у animal
много полезных свойств и методов, то они автоматически становятся доступными у rabbit
. Такие
свойства называются «унаследованными».
Если у нас есть метод в animal
, он может быть вызван на rabbit
:
let animal = {
eats: true,
walk() {
console.log('Animal walk');
}
}
let rabbit = {
jumps: true,
__proto__: animal
}
// walk взят из прототипа
rabbit.walk(); // -> Animal walk
Метод автоматически берется из прототипа. Цепочка прототипов может быть длиннее:
let animal = {
eats: true,
walk() {
console.log('Animal walk');
}
}
let rabbit = {
jumps: true,
__proto__: animal
}
let longEar = {
earLength: 10,
__proto__: rabbit
}
// walk взят из цепочки прототипов
longEar.walk(); // -> Animal walk
console.log(longEar.jumps); // -> true (из rabbit)
Есть некоторые ограничения:
- Ссылки не могут идти по кругу.
- Значение
__proto__
может быть объектом илиnull
. Другие типы игнорируются. - Может быть только один
[[Prototype]]
. Объект не может наследоваться от двух других объектов.
Операция записи не использует прототип
Прототип используется только для чтения свойств.
Операции записи/удаления работают напрямую с объектом.
В приведенном ниже примере мы присваиваем rabbit
собственный метод walk
:
let animal = {
eats: true,
walk() {
/* этот метод не будет использоваться в rabbit */
}
};
let rabbit = {
__proto__: animal
};
rabbit.walk = function () {
console.log("Rabbit! Bounce-bounce!");
};
rabbit.walk(); // -> Rabbit! Bounce-bounce!
Теперь вызов rabbit.walk()
находит метод непосредственно в объекте и выполняет его, не используя прототип.
Значение this
Прототипы никак не влияют на this
.
Неважно, где находится метод: в объекте или его прототипе. При вызове метода
this
— всегда объект перед точкой.
Например, здесь animal
представляет собой «хранилище методов», и rabbit
использует его.
Вызов rabbit.sleep()
устанавливает this.isSleeping
для объекта rabbit
:
// методы animal
let animal = {
walk() {
if (!this.isSleeping) {
console.log('I walk');
}
},
sleep() {
this.isSleeping = true;
}
};
let rabbit = {
name: 'White Rabbit',
__proto__: animal,
}
// модифицирует rabbit.isSleeping
rabbit.sleep();
console.log(rabbit.isSleeping); // -> true
console.log(animal.isSleeping); // -> undefined (нет такого свойства в прототипе)
Итого
- В JavaScript все объекты имеют скрытое свойство
[[Prototype]]
, которое является либо другим объектом, либоnull
. - Мы можем использовать
obj.__proto__
для доступа к[[Prototype]]
. - Объект, на который ссылается
[[Prototype]]
, называется «прототипом». - Если мы хотим прочитать свойство
obj
или вызвать метод, которого не существует уobj
, тогда JavaScript попытается найти его в прототипе. - Операции записи/удаления работают непосредственно с объектом, они не используют прототип.
- Если мы вызываем
obj.method()
, а метод при этом взят из прототипа, тоthis
все равно ссылается наobj
.
Конспект статьи из учебника по JavaScript — Прототипное наследование