Прототипы, наследование
В программировании часто возникает необходимость что-то расширить.
Например у нас есть объект 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 — Прототипное наследование