Конструкторы, прототипы
Открытый бесплатный вебинар об ООП в JavaScript
Первая часть: https://studyjavascript.blogspot.com/2019/02/javascript.html
Конструкторы
Если вызвать функцию с ключевым словом new. то функция начнёт возвращать "пустой" объект.var F = function(){
}
new F() // Object {}
Такая функция называется конструктором, а объект, который она возвращает, экземпляром.
this внутри этой функции будет указывать на экземпляр. Соответственно, используя this можно внутри этой функции выполнять манипуляции с объектом, например, добавлять ему свойства.
var F = function(name){
this.name = name;
}
var name1 = new F("Anton"); // Object { name="Anton"}
var name2 = new F("Boris"); // Object { name="Boris"}
Каждый раз при вызове функции с ключевым словом new возвращается новый объект со своим набором полей.
Внутри экземпляра, как и любого другого объекта, всегда есть свойство constructor, которое содержит ссылку на функцию-конструктор данного объекта.
var F = function(name){
this.name = name;
}
var name1 = new F("Anton");
name1.constructor
Результат:
ƒ (name){
this.name = name;
}
Предположим, что внутри конструктора мы хотим создать методы объекта. Как это сделать:
var F = function(name){
this.setName = function(name){
this.name = name;
}
this.getName = function(){
return this.name;
}
this.name = name; // лучше заменить на this.setName();
}
var fn = new F("Сергей");
fn.getName(); // "Сергей"
fn.setName("Андрей"); // установили новое имя
fn.getName(); // "Андрей"
Ещё один способ наполнять конструктор методами — использовать свойство prototype.
Рrototype
У любой функции-конструктора есть свойство prototype. по умолчанию это пустой объект. Убедимся в этом:var F = function(name){
this.setName = function(name){
this.name = name;
}
this.getName = function(){
return this.name;
}
this.name = name; // лучше заменить на this.setName();
}
F.prototype // Object {}
F.prototype возвращает пустой объект.
Перепишем нашу функцию следующим образом:
var F = function(name){
F.prototype.setName = function(name){
this.name = name;
}
F.prototype.getName = function(){
return this.name;
}
this.name = name; // лучше заменить на this.setName();
}
В данном случае мы добавляем новые функции не в сам объект, а в свойство prototype конструктора.
Добавим вызов new F и убедимся, что код работает как и прежде
var fn = new F("Сергей");
fn.getName(); // "Сергей"
fn.setName("Андрей"); // установили новое имя
fn.getName(); // "Андрей"
Посмотрим чему теперь равно F.prototype
F.prototype // {setName: ƒ, getName: ƒ, constructor: ƒ}
Прототипное наследование
У каждого конструктора есть свойство prototype, которое изначально является "пустым" объектом. То есть когда мы вызываем функцию с ключевым словом new, конструктор создаёт нам новый "пустой" объект в котором есть скрытое свойство __proto__.
Наполняя prototype свойствами, мы, тем самым, наполняем теми же свойствами все экземпляры данного конструктора.
Когда конструктор создаёт экземпляр, помимо свойства constructor в экземпляр помещается скрытое свойство __proto__ . которое указывает на prototype конструктора.
F.prototype === fn.__proto__ // true
Прямой доступ к __proto__ доступен не во всех браузерах.
Схематически. после вызова
var fn = new F("Сергей");
функция fn имеет вид:
fn = {
name: "Сергей",
__proto__: {
getName = function(){};
setName = function(name){};
}
}
Когда мы обращаемся к свойству экземпляоа, вначале это свойство ищется в самом объекте, а потом в в служебном свойстве __proto__.
Кстати, свойство constructor располагается в prototype конструктора, а не добавляется при его создании.
Пример наследования
Представим, что нам нужно создать ещё один экземпляр объекта, который
обладал бы всеми свойствами исходного конструктора, но также имел бы
какие-то отличительные свойства.
Кажется, что достаточно присвоить прототипу объекта F2 прототип объекта F1, а затем добавить в функцию несколько новых методов.
F2.prototype = F1.prototype;
Для решения используют функцию-прослойку из пустого объекта таким образом, чтобы изменения в прототипе потомка не влияли на родительский прототип.
Вот как это выглядит
function inherit(child, parent) {
var Temp = function () {
};
Temp.prototype = parent.prototype;
child.prototype = new Temp();
}
Теперь применим эту функцию
inherit(F2, F1);
То есть наследуем F2 от F1
В текущей версии JS, всю функцию inherit можно сильно укоротить
function inherit(child, parent) {
child.prototype = Object.create(parent.prototype);
}
Object.create как раз создает ту самую прослойку на основе переданного прототипа родителя.
Ещё один вариант передать контекст - использовать стрелочную функцию, у которой своего контекста нет, но которая наследует контекст родителя.
function inherit(child, parent) {
child.prototype = () => parent.prototype;
}