Введение
Прототипы — это механизм, с помощью которого объекты JavaScript наследуют свойства друг от друга. В этой статье мы объясним, что такое прототип, как работают цепочки прототипов и как установить прототип для объекта.
Предпосылки
Разберитесь в функциях JavaScript, ознакомьтесь с основами JavaScript (см. «Начало работы» и «Основные блоки») и принципами объектно-ориентированного программирования (см. «Введение в объекты»).
Прототип цепи
Попробуйте создать литеральный объект в консоли браузера:
const myObject = {
city: "Madrid",
greet() {
console.log(`Greetings from ${this.city}`);
},
};
myObject.greet(); // Greetings from MadridЭто объект с одним свойством данных — city — и одним методом — greet(). Если вы введете в консоль имя объекта, а затем точку, например, myObject., консоль отобразит список всех доступных свойств этого объекта. Вы увидите, что помимо city и greeting есть гораздо больше свойств!
__defineGetter__
__defineSetter__
__lookupGetter__
__lookupSetter__
__proto__
city
constructor
greet
hasOwnProperty
isPrototypeOf
propertyIsEnumerable
toLocaleString
toString
valueOfПопробуйте получить доступ к одному из них:
myObject.toString(); // "[object Object]"
Это работает (даже если не совсем понятно, что делает функция toString()).
Что это за дополнительные свойства и откуда они берутся?
В JavaScript каждый объект имеет встроенное свойство, называемое прототипом. Прототип сам по себе является объектом, поэтому у каждого прототипа будет свой собственный прототип, создавая так называемую цепочку прототипов. Эта цепочка обрывается, когда мы достигаем прототипа, у которого в качестве прототипа указано значение null.
Примечание: Свойство объекта, указывающее на его прототип, не называется прототипом. Это название не является стандартным, но на практике все браузеры используют __proto__. Стандартный способ доступа к прототипу объекта — это метод Object.getPrototypeOf().
При попытке доступа к свойству объекта: если свойство не найдено в самом объекте, выполняется поиск в прототипе свойства. Если свойство по-прежнему не найдено, выполняется поиск в прототипе экземпляра, и так далее, пока свойство не будет найдено или не будет достигнут конец цепочки, в этом случае возвращается значение undefined.
Таким образом, когда мы вызываем метод myObject.toString(), браузер:
- Ищет toString в myObject
- Я не могу найти его там, поэтому он ищет `toString` в прототипе объекта `myObject`.
- Он находит его там и делает звонок.
Какой прототип у объекта myObject? Чтобы это выяснить, мы можем использовать функцию Object.getPrototypeOf():
Object.getPrototypeOf(myObject); // Object { }
Это объект с именем Object.prototype, представляющий собой самый базовый прототип, который есть у всех объектов по умолчанию. Прототип Object.prototype равен null, поэтому он находится в конце цепочки прототипов:
Прототип объекта не всегда равен Object.prototype. Попробуйте следующее:
const myDate = new Date();
let object = myDate;
do {
object = Object.getPrototypeOf(object);
console.log(object);
} while (object);
// Date.prototype
// Object { }
// nullЭтот код создает объект Date, затем перемещается вверх по цепочке прототипов и регистрирует прототипы. Он показывает нам, что прототипом объекта myDate является объект Date.prototype, а его прототипом — Object.prototype.
Фактически, когда вы вызываете знакомые методы, такие как myDate2.getTime(), вы вызываете метод, определенный в Date.prototype.
свойства затенения
Что произойдет, если определить свойство объекта, если в прототипе этого же объекта уже определено свойство с тем же именем? Давайте посмотрим:
const myDate = new Date(1995, 11, 17);
console.log(myDate.getTime()); // 819129600000
myDate.getTime = function () {
console.log("something else!");
};
myDate.getTime(); // 'something else!'Это должно быть предсказуемо, учитывая описание цепочки прототипов. Когда мы вызываем getTime(), браузер сначала ищет свойство с таким именем в myDate и проверяет прототип только в том случае, если myDate его не определяет. Поэтому, когда мы добавляем getTime() к myDate, вызывается версия из myDate.
Это называется “затенением” объекта недвижимости.
Создание прототипа
В JavaScript существует несколько способов задать прототип объекта, и здесь мы опишем два из них: Object.create() и конструктор.
Использование Object.create
Метод Object.create() создает новый объект и позволяет указать объект, который будет использоваться в качестве прототипа нового объекта.
Вот пример:
const personPrototype = {
greet() {
console.log("hello!");
},
};
const carl = Object.create(personPrototype);
carl.greet(); // hello!Здесь мы создаём объект personPrototype, имеющий метод greet(). Затем мы используем Object.create() для создания нового объекта с personPrototype в качестве прототипа. Теперь мы можем вызвать greet() для нового объекта, и прототип предоставит его реализацию.
Использование конструктора
В JavaScript у всех функций есть свойство с именем prototype. Когда вы вызываете функцию в качестве конструктора, этому свойству присваивается прототип вновь созданного объекта (по соглашению, в свойстве с именем __proto__).
Таким образом, если мы зададим прототип конструктора, мы сможем гарантировать, что всем объектам, созданным с помощью этого конструктора, будет присвоен этот прототип:
const personPrototype = {
greet() {
console.log(`hello, my name is ${this.name}!`);
},
};
function Person(name) {
this.name = name;
}
Object.assign(Person.prototype, personPrototype);
// or
// Person.prototype.greet = personPrototype.greet;Здесь мы творим:
- Объект personPrototype, имеющий метод greet().
- Функция-конструктор Person(), которая инициализирует имя создаваемого человека.
Затем мы помещаем методы, определенные в personPrototype, в свойство prototype функции Person, используя Object.assign.
После выполнения этого кода объекты, созданные с помощью Person.prototype(), получат в качестве своего прототипа объект, который автоматически будет содержать метод greet.
const reuben = new Person("Reuben");
reuben.greet(); // hello, my name is Reuben!Это также объясняет, почему мы ранее говорили, что прототип объекта myDate называется Date.prototype: это свойство прототипа конструктора объекта Date.
Собственная собственность
Создаваемые нами объекты с помощью конструктора Person, описанного выше, имеют два свойства:
- Свойство name, задаваемое в конструкторе, чтобы оно отображалось непосредственно в объектах Person.
- Метод `greet()`, заданный в прототипе.
Часто встречается такая схема, когда методы определяются в прототипе, а свойства данных — в конструкторе. Причина в том, что методы обычно одинаковы для каждого создаваемого объекта, в то время как нам часто нужно, чтобы каждый объект имел своё уникальное значение для своих свойств данных (как в данном случае, у каждого человека разное имя).
Свойства, определенные непосредственно в объекте, например, имя в данном случае, называются собственными свойствами, и вы можете проверить, является ли свойство конкретным, используя статический метод Object.hasOwn():
const irma = new Person("Irma");
console.log(Object.hasOwn(irma, "name")); // true
console.log(Object.hasOwn(irma, "greet")); // falseПримечание: Здесь также можно использовать нестатический метод Object.hasOwnProperty(), но мы рекомендуем использовать Object.hasOwn(), если это возможно.
Прототипы и наследование
Прототипы — это мощная и очень гибкая функция JavaScript, которая позволяет повторно использовать код и создавать объекты.
Они специально поддерживают один из вариантов наследования. Наследование — это особенность объектно-ориентированных языков программирования, которая позволяет программистам выразить идею о том, что некоторые объекты в системе являются более специализированными версиями других объектов.
Например, если мы моделируем школу, у нас могут быть учителя и ученики: они оба люди, поэтому у них есть общие свойства (например, у обоих есть имена), но каждый может добавлять дополнительные свойства (например, у учителей есть предмет, который они преподают), или они могут реализовывать одно и то же свойство разными способами. В объектно-ориентированной системе мы могли бы сказать, что и учителя, и ученики наследуют от класса People.
Вы можете увидеть, как в JavaScript, если объекты Professor и Student могут иметь прототипы Person, они могут наследовать общие свойства, одновременно добавляя и переопределяя свойства, которые должны отличаться.
В следующей статье мы обсудим наследование, а также другие ключевые особенности объектно-ориентированных языков программирования и посмотрим, как их поддерживает JavaScript.
Результат
В этой статье рассматриваются прототипы объектов JavaScript, в том числе то, как цепочки прототипов объектов позволяют объектам наследовать свойства друг от друга, свойство прототипа и как использовать его для добавления методов в конструкторы, а также другие смежные темы.









