Конструкторы, прототипы



Открытый бесплатный вебинар об ООП в 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;
 
И это действительно работает. Но при этом новые методы функции F2 становятся доступными в F1. это объясняется тем, что функции, как и любые другие объекты передаются по ссылке, поэтому изменяя функцию в F2 мы изменяем её же в F1

Для решения используют функцию-прослойку из пустого объекта таким образом, чтобы изменения в прототипе потомка не влияли на родительский прототип.
Вот как это выглядит


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;
}