Операция записи не использует прототип
Прототип используется только для чтения свойств.
Операции записи/удаления работают напрямую с объектом.
В приведённом ниже примере мы присваиваем 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, они сохраняются в этих объектах.
В результате методы являются общими, а состояние объекта — нет.