На одном из
предыдущих занятий по JavaScript мы с вами
говорили о возможности создания объектов с помощью оператора new через
специальную функцию-конструктор, например, так:
function Rect() {
this.name = "прямоугольник";
this.draw = function() {
console.log("Рисование фигуры: "+this.name);
}
}
let r = new Rect();
r.draw();
Как здесь
указывать базовый объект, который бы расширяла функция Rect? Для этого
также можно воспользоваться свойством __proto__, о котором мы
начали говорить на предыдущем занятии.
Допустим, выше имеется
объявление объекта prop:
let prop = {
sp: {x: 0, y: 0},
ep: {x: 100, y: 20},
get coords() {
return [this.sp.x, this.sp.y, this.ep.x, this.ep.y];
},
set coords(coords) {
this.sp.x = coords[0]; this.sp.y = coords[1];
this.ep.x = coords[2]; this.ep.y = coords[3];
}
};
и, далее, в
функции добавим строчку:
Все, мы
установили в качестве базового объекта – объект prop. В этом легко
убедиться, если перебрать свойства объекта r:
for(let p in r)
console.log(p+': '+r[p]);
Как видите, в
консоли отобразились атрибуты, связанные и с объектом prop. Однако, в
ранних скриптах можно встретить и другую запись:
Причем,
обращение к свойству prototype происходит вне функции. Это
имеет принципиальное значение. Если поместить эту конструкцию внутрь:
то будет
попросту создано свойство с таким именем и значением ссылки на объект prop. Получается,
что prototype – это не совсем прототип объекта, подобно другому свойству __proto__, которое можно
использовать и после создания объекта. Здесь же важно, чтобы мы определили
свойство prototype до вызова оператора new. Тогда в
процессе создания объекта Rect объект, указанный в prototype
автоматически станет базовым для Rect. Мало того, в таком качестве свойство prototype
используется только в момент вызова оператора new. В последующем,
его можно совершенно спокойно видоизменять и это никак не скажется на связке
«базовый объект – дочерний объект». Например, после создания объекта r, изменим это
свойство:
и при выводе
свойств для r видим в консоли
все атрибуты базового объекта и еще свойство prototype со значением hello. То есть,
механизм наследования никак не был затронут после создания объекта r и изменения prototype.
Связь между __proto__ и prototype
Поведение
свойств __proto__ и prototype очень похоже,
следовательно, между ними должна быть какая-то связь. И, действительно, __proto__ это аксессор
объекта prototype:
То есть, когда
встречается конструкция:
то уже после
создания объекта устанавливается связь с базовым объектом prop. Или же, перед
созданием объекта Rect можно записать строчку:
Rect.prototype.__proto__ = prop;
и тогда в момент
создания текущему объекту будет назначен базовый prop. Причем, автоматически
будет создан некий базовый объект для r, в котором через свойство __proto__ устанавливается
связь на объект prop:
Добавление свойств через prototype
Итак, мы только
что увидели, что поведение свойства prototype несколько более
изощренное, чем можно было подумать на первый взгляд. Например, зададимся
вопросом: что будет, если в этом объекте создать новые свойства:
Rect.prototype.__proto__ = prop;
Rect.prototype.getName = function() { return this.name; }
Rect.prototype.setName = function(name) { this.name = name; }
То есть, теперь prototype ссылается не на
какой-то конкретный объект, а являясь сам по себе объектом, приобретет
дополнительные свойства: prop, getName и setName. В этом случае,
новый созданный объект Rect будет иметь доступ ко всем этими
свойствам, расположенные в базовом объекте, а базовый объект, в свою очередь,
будет ссылаться на объект prop:
let r = new Rect();
console.log( r.prop.coords );
console.log( r.getName() );
Свойство constructor
Каждая функция в
JavaScript уже хранит в объекте
prototype некоторую служебную информацию. Например, если выполнить строчку:
console.log(Rect.prototype)
то мы увидим в
этом объекте свойства:
-
constructor – ссылка на
функцию-конструктор для создания объекта;
-
__proto__ – объект,
определяющий объект-прототип.
Поэтому, изменяя
его:
мы уничтожаем
эту информацию. В чем легко убедиться, если попытаться вывести в консоль
результат следующей проверки:
console.log(r.constructor == Rect) //false
и после
комментирования строки:
// Rect.prototype = prop;
Увидим
совершенно разные результаты: false и true. Чтобы
выполнить наследование объектов с использованием prototype и сохранить
информацию о конструкторе, можно переопределить это свойство при создании
объекта, то есть, внутри функции Rect записать строчку:
Rect.prototype.constructor = Rect;
Тем самым мы
восстановим это значение. Но зачем оно вообще нужно? Теряется и пусть теряется.
Дело в том, что в больших и сложных программах может потребоваться создать
подобный объект. Здесь как раз хорошо подходит это свойство:
let r2 = new r.constructor();
В ряде случаев,
такой подход очень удобен. И чтобы иметь эту функциональность, мы и сохраняем
свойство constructor в prototype.