До этого момента мы говорили о наследовании объектов, объявленных через {...}
.
Но в реальных проектах объекты обычно создаются функцией-конструктором через new
. Посмотрим, как указать прототип в этом случае.
Свойство F.prototype
Самым очевидным решением является назначение __proto__
в конструкторе.
Например, если я хочу, чтобы у всех объектов, которые создаются new Rabbit
, был прототип animal
, я могу сделать так:
var animal = {
eats: true
};
function Rabbit(name) {
this.name = name;
this.__proto__ = animal;
}
var rabbit = new Rabbit("Кроль");
alert( rabbit.eats ); // true, из прототипа
Недостаток этого подхода – он не работает в IE10-.
К счастью, в JavaScript с древнейших времён существует альтернативный, встроенный в язык и полностью кросс-браузерный способ.
Чтобы новым объектам автоматически ставить прототип, конструктору ставится свойство prototype
.
При создании объекта через new
, в его прототип __proto__
записывается ссылка из prototype
функции-конструктора.
Например, код ниже полностью аналогичен предыдущему, но работает всегда и везде:
var animal = {
eats: true
};
function Rabbit(name) {
this.name = name;
}
Rabbit.prototype = animal;
var rabbit = new Rabbit("Кроль"); // rabbit.__proto__ == animal
alert( rabbit.eats ); // true
Установка Rabbit.prototype = animal
буквально говорит интерпретатору следующее: «При создании объекта через new Rabbit
запиши ему __proto__ = animal
».
prototype
имеет смысл только у конструктораСвойство с именем prototype
можно указать на любом объекте, но особый смысл оно имеет, лишь если назначено функции-конструктору.
Само по себе, без вызова оператора new
, оно вообще ничего не делает, его единственное назначение – указывать __proto__
для новых объектов.
prototype
может быть только объектТехнически, в это свойство можно записать что угодно.
Однако, при работе new
, свойство prototype
будет использовано лишь в том случае, если это объект. Примитивное значение, такое как число или строка, будет проигнорировано.
Свойство constructor
У каждой функции по умолчанию уже есть свойство prototype
.
Оно содержит объект такого вида:
function Rabbit() {}
Rabbit.prototype = {
constructor: Rabbit
};
В коде выше я создал Rabbit.prototype
вручную, но ровно такой же – генерируется автоматически.
Проверим:
function Rabbit() {}
// в Rabbit.prototype есть одно свойство: constructor
alert( Object.getOwnPropertyNames(Rabbit.prototype) ); // constructor
// оно равно Rabbit
alert( Rabbit.prototype.constructor == Rabbit ); // true
Можно его использовать для создания объекта с тем же конструктором, что и данный:
function Rabbit(name) {
this.name = name;
alert( name );
}
var rabbit = new Rabbit("Кроль");
var rabbit2 = new rabbit.constructor("Крольчиха");
Эта возможность бывает полезна, когда, получив объект, мы не знаем в точности, какой у него был конструктор (например, сделан вне нашего кода), а нужно создать такой же.
constructor
легко потерятьJavaScript никак не использует свойство constructor
. То есть, оно создаётся автоматически, а что с ним происходит дальше – это уже наша забота. В стандарте прописано только его создание.
В частности, при перезаписи Rabbit.prototype = { jumps: true }
свойства constructor
больше не будет.
Сам интерпретатор JavaScript его в служебных целях не требует, поэтому в работе объектов ничего не «сломается». Но если мы хотим, чтобы возможность получить конструктор, всё же, была, то можно при перезаписи гарантировать наличие constructor
вручную:
Rabbit.prototype = {
jumps: true,
constructor: Rabbit
};
Либо можно поступить аккуратно и добавить свойства к встроенному prototype
без его замены:
// сохранится встроенный constructor
Rabbit.prototype.jumps = true
Эмуляция Object.create для IE8-
Как мы только что видели, с конструкторами всё просто, назначить прототип можно кросс-браузерно при помощи F.prototype
.
Теперь небольшое «лирическое отступление» в область совместимости.
Прямые методы работы с прототипом отсутствуют в старых IE, но один из них – Object.create(proto)
можно эмулировать, как раз при помощи prototype
. И он будет работать везде, даже в самых устаревших браузерах.
Кросс-браузерный аналог – назовём его inherit
, состоит буквально из нескольких строк:
function inherit(proto) {
function F() {}
F.prototype = proto;
var object = new F;
return object;
}
Результат вызова inherit(animal)
идентичен Object.create(animal)
. Она создаёт новый пустой объект с прототипом animal
.
Например:
var animal = {
eats: true
};
var rabbit = inherit(animal);
alert( rabbit.eats ); // true
Посмотрите внимательно на функцию inherit
и вы, наверняка, сами поймёте, как она работает…
Если где-то неясности, то её построчное описание:
function inherit(proto) {
function F() {} // (1)
F.prototype = proto // (2)
var object = new F; // (3)
return object; // (4)
}
- Создана новая функция
F
. Она ничего не делает сthis
, так что если вызватьnew F
, то получим пустой объект. - Свойство
F.prototype
устанавливается в будущий прототипproto
- Результатом вызова
new F
будет пустой объект с__proto__
равным значениюF.prototype
. - Мы получили пустой объект с заданным прототипом, как и хотели. Возвратим его.
Для унификации можно запустить такой код, и метод Object.create
станет кросс-браузерным:
if (!Object.create) Object.create = inherit; /* определение inherit - выше */
В частности, аналогичным образом работает библиотека es5-shim, при подключении которой Object.create
станет доступен для всех браузеров.
Итого
Для произвольной функции – назовём её Person
, верно следующее:
- Прототип
__proto__
новых объектов, создаваемых черезnew Person
, можно задавать при помощи свойстваPerson.prototype
. - Значением
Person.prototype
по умолчанию является объект с единственным свойствомconstructor
, содержащим ссылку наPerson
. Его можно использовать, чтобы из самого объекта получить функцию, которая его создала. Однако, JavaScript никак не поддерживает корректность этого свойства, поэтому программист может его изменить или удалить. - Современный метод
Object.create(proto)
можно эмулировать при помощиprototype
, если хочется, чтобы он работал в IE8-.