Операция записи не использует прототип
Прототип используется только для чтения свойств.
Операции записи/удаления работают напрямую с объектом.
В приведённом ниже примере мы присваиваем rabbit
собственный метод walk
:
let animal = {
eats: true,
walk() {
/* этот метод не будет использоваться в rabbit */
}
};
let rabbit = {
__proto__: animal
};
rabbit.walk = function() {
alert("Rabbit! Bounce-bounce!");
};
rabbit.walk(); // Rabbit! Bounce-bounce!
Теперь вызов rabbit.walk()
находит метод непосредственно в объекте и выполняет его, не используя прототип:
Свойства-аксессоры – исключение, так как запись в него обрабатывается функцией-сеттером. То есть, это, фактически, вызов функции.
По этой причине admin.fullName
работает корректно в приведённом ниже коде:
let user = {
name: "John",
surname: "Smith",
set fullName(value) {
[this.name, this.surname] = value.split(" ");
},
get fullName() {
return `${this.name} ${this.surname}`;
}
};
let admin = {
__proto__: user,
isAdmin: true
};
alert(admin.fullName); // John Smith (*)
// срабатывает сеттер!
admin.fullName = "Alice Cooper"; // (**)
alert(admin.name); // Alice
alert(admin.surname); // Cooper
Здесь в строке (*)
свойство admin.fullName
имеет геттер в прототипе user
, поэтому вызывается он. В строке (**)
свойство также имеет сеттер в прототипе, который и будет вызван.
Значение «this»
В приведённом выше примере может возникнуть интересный вопрос: каково значение this
внутри set fullName(value)
? Куда записаны свойства this.name
и this.surname
: в user
или в admin
?
Ответ прост: прототипы никак не влияют на this
.
Неважно, где находится метод: в объекте или его прототипе. При вызове метода this
— всегда объект перед точкой.
Таким образом, вызов сеттера admin.fullName=
в качестве this
использует admin
, а не user
.
Это на самом деле очень важная деталь, потому что у нас может быть большой объект со множеством методов, от которого можно наследовать. Затем наследующие объекты могут вызывать его методы, но они будут изменять своё состояние, а не состояние объекта-родителя.
Например, здесь animal
представляет собой «хранилище методов», и rabbit
использует его.
Вызов rabbit.sleep()
устанавливает this.isSleeping
для объекта rabbit
:
// методы animal
let animal = {
walk() {
if (!this.isSleeping) {
alert(`I walk`);
}
},
sleep() {
this.isSleeping = true;
}
};
let rabbit = {
name: "White Rabbit",
__proto__: animal
};
// модифицирует rabbit.isSleeping
rabbit.sleep();
alert(rabbit.isSleeping); // true
alert(animal.isSleeping); // undefined (нет такого свойства в прототипе)
Картинка с результатом:
Если бы у нас были другие объекты, такие как bird
, snake
и т.д., унаследованные от animal
, они также получили бы доступ к методам animal
. Но this
при вызове каждого метода будет соответствовать объекту (перед точкой), на котором происходит вызов, а не animal
. Поэтому, когда мы записываем данные в this
, они сохраняются в этих объектах.
В результате методы являются общими, а состояние объекта — нет.