Почему наедаются оба хомяка?
У нас есть два хомяка: шустрый (speedy) и ленивый (lazy); оба наследуют от общего объекта hamster.
Когда мы кормим одного хомяка, второй тоже наедается. Почему? Как это исправить?
let hamster = {
stomach: [],
eat(food) {
this.stomach.push(food);
}
};
let speedy = {
__proto__: hamster
};
let lazy = {
__proto__: hamster
};
// Этот хомяк нашёл еду
speedy.eat("apple");
alert( speedy.stomach ); // apple
// У этого хомяка тоже есть еда. Почему? Исправьте
alert( lazy.stomach ); // apple
Давайте внимательно посмотрим, что происходит при вызове speedy.eat("apple").
-
Сначала в прототипе (
=hamster) находится методspeedy.eat, а затем он выполняется сthis=speedy(объект перед точкой). -
Затем в
this.stomach.push()нужно найти свойствоstomachи вызвать для негоpush. Движок ищетstomachвthis(=speedy), но ничего не находит. -
Он идёт по цепочке прототипов и находит
stomachвhamster. -
И вызывает для него
push, добавляя еду в живот прототипа.
Получается, что у хомяков один живот на двоих!
И при lazy.stomach.push(...) и при speedy.stomach.push(), свойство stomach берётся из прототипа (так как его нет в самом объекте), затем в него добавляются данные.
Обратите внимание, что этого не происходит при простом присваивании this.stomach=:
let hamster = {
stomach: [],
eat(food) {
// присвоение значения this.stomach вместо вызова this.stomach.push
this.stomach = [food];
}
};
let speedy = {
__proto__: hamster
};
let lazy = {
__proto__: hamster
};
// Шустрый хомяк нашёл еду
speedy.eat("apple");
alert( speedy.stomach ); // apple
// Живот ленивого хомяка пуст
alert( lazy.stomach ); // <ничего>
Теперь всё работает правильно, потому что this.stomach= не ищет свойство stomach. Значение записывается непосредственно в объект this.
Также мы можем полностью избежать проблемы, если у каждого хомяка будет собственный живот:
let hamster = {
stomach: [],
eat(food) {
this.stomach.push(food);
}
};
let speedy = {
__proto__: hamster,
stomach: []
};
let lazy = {
__proto__: hamster,
stomach: []
};
// Шустрый хомяк нашёл еду
speedy.eat("apple");
alert( speedy.stomach ); // apple
// Живот ленивого хомяка пуст
alert( lazy.stomach ); // <ничего>
Все свойства, описывающие состояние объекта (как свойство stomach в примере выше), рекомендуется записывать в сам этот объект. Это позволяет избежать подобных проблем.