Рис. 18.5. Добавление промежуточного объекта person со свойством (теперь используемым совместно) getName
Заметьте, что теперь person стал частью цепочки прототипов, удачно расположившись между Object.prototype и нашими дочерними объектами. Как же это делается? Один из подходов мы уже видели ранее, и в нем мы опираемся на Object.create. При использовании Object.create мы можем указать объект, на основе которого требуется создать новый объект. Например:
let myObject = Object.create(fooObject);
Когда мы это делаем, за кадром происходит следующее: прототип нашего объекта myObject теперь будет fooObject. При этом он становится частью цепочки прототипов. Теперь, когда мы сделали крюк и расширили наше понимание Object.create, освоив содержание этой главы. Давайте вернемся к изначальному вопросу о том, как же именно наши объекты funnyGuy, theDude и detective наследуют от person.
Код, осуществляющий все это, будет таким:
let person = {
getName: function () {
return "The name is " + this.firstName + " " + this.lastName;
}
};
let funnyGuy = Object.create(person);
funnyGuy.firstName = "Conan";
funnyGuy.lastName = "O'Brien";
let theDude = Object.create(person);
theDude.firstName = "Jeffrey";
theDude.lastName = "Lebowski";
let detective = Object.create(person);
detective.firstName = "Adrian";
detective.lastName = "Monk";
Принцип работы цепочки прототипов позволяет нам вызывать getName для любого из наших объектов funnyGuy, theDude или detective, что приведет к ожидаемому результату:
detective.getName(); // Имя Adrian Monk
Если мы решим расширить объект person, то достаточно сделать это всего один раз, и это также отразится на всех наследующих от него объектах, не требуя дополнительного повторения. Предположим, мы хотим добавить метод getInitials, возвращающий первую букву из имени и фамилии:
let person = {
getName: function () {
return "The name is " + this.firstName + " " + this.lastName;
},
getInitials: function () {
if (this.firstName && this.lastName) {
return this.firstName[0] + this.lastName[0];
}
}
};
Мы добавляем метод getInitials в объект person. Чтобы использовать этот метод, можем вызвать его для любого объекта, расширяющего person, например funnyGuy:
funnyGuy.getInitials(); // CO
Такая возможность создавать промежуточные объекты, помогающие разделять функциональность кода, является мощным инструментом. Она повышает эффективность создания объектов и добавления в них функциональности. Неплохо, правда?
Ключевое слово this
В предыдущих фрагментах кода вы могли заметить использование ключевого слова this, особенно в случае с объектом person, где мы задействовали его для обращения к свойствам, созданным в его потомках, а не к его собственным. Давайте вернемся к этому объекту, а в частности к его свойству getName:
let person = {
getName: function () {
return "The name is " + this.firstName + " " + this.lastName;
},
getInitials: function () {
if (this.firstName && this.lastName) {
return this.firstName[0] + this.lastName[0];
}
}
};
Когда мы вызываем getName, то возвращаемое имя будет зависеть от того, из какого объекта мы это делаем. Например, если мы сделаем следующее:
let spaceGuy = Object.create(person);
spaceGuy.firstName = "Buzz";
spaceGuy.lastName = "Lightyear";
console.log(spaceGuy.getName()); // Buzz Lightyear
При выполнении этого кода мы увидим в консоли Buzz Lightyear. Если мы еще раз взглянем на свойство getName, то увидим, что там нет свойств firstName и lastName в объекте person. Но как мы видели ранее, если свойство не существует, мы переходим далее по цепочке от родителя к родителю, как показано на рис. 18.6.
Рис. 18.6. Цепочка прототипов для объекта person
В нашем случае единственной остановкой в цепочке будет Object.prototype, но в нем также не обнаруживаются свойства firstName и lastName. Как же тогда метод getName умудряется сработать и вернуть нужные значения?
Ответ заключается в ключевом слове this, предшествующем firstName и lastName в инструкции return метода getName:
let person = {
getName: function () {
return "The name is " + this.firstName + " " + this.lastName;
},
getInitials: function () {