До этого момента мы говорили о наследовании объектов, объявленных через {...}.
Но в реальных проектах объекты обычно создаются функцией-конструктором через 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-.
Комментарии
<code>, для нескольких строк кода — тег<pre>, если больше 10 строк — ссылку на песочницу (plnkr, JSBin, codepen…)