g1er / Andrew

0 stars 0 forks source link

Унаследование классов при помощи extends #12

Open IgorKulishov opened 6 years ago

IgorKulishov commented 6 years ago

Мы переходим к завершающей теме классов ES6 наследования классов. Я руководствуюсь источником из https://learn.javascript.ru/es-class Наследование классов осуществляется при помощи синтаксиса extends

class Child extends Parent {
  ...
}

То что написано выше - класс Child (дословно) расширяет/продолжает/унаследует класс Parent. В объектном ориентированных языках (Java, C) это означает что все методы и свойства родительского класса Parent будет унаследованы дочерним классом Child, но при этом можно модифицировать методы родительского класса Parent и кроме того добавлять новые методы.

Разберем пример:

class Animal {
  constructor(name) {
    this.name = name;
  }

  walk() {
    alert("I walk: " + this.name);
  }
}

//default 
class Cat extends Animal {

}

new Cat("Mayu").walk();

//modified method
class Rabbit extends Animal {
  walk() {
    super.walk();
    alert("...and jump!");
  }
}

new Rabbit("Jorge").walk();

В данном примере класс Rabit является дочерним классом и наследует свойства и методы класса Animal. При этом а) понятно что class Rabbit будет иметь так же конструктор где будет определяться свойство name б) кроме того мы переписываем метод walk внутри которого мы ссылаемся на метод родительского класса при помощи оператора super

  walk() {
    super.walk();
    alert("...and jump!");
  }

и кроме того добавляем в метод walk() дополнительно встроенную JS ф-цию alert("...and jump!"); хотя мы могли бы и не вызывать метод super.walk() и оставить только наш новый функционал. Таким образом при унаследовании мы унаследуем все методы , но имеем возможность полностью поменять их функционал.

Примечание: Как написано по ссылке , super может быть вызыван только изнутри дочернего класса.

IgorKulishov commented 6 years ago

Я дал тебе минимум, если будет время почитай источник из https://learn.javascript.ru/es-class Напиши если будут вопросы по книгу или по моему разъяснению.

g1er commented 6 years ago

Еще раз пробежался по статье, вспомнил уже прошедшее, прочел про наследование. У меня вопрос про super. Я раньше о нем не слышал, но в статейке прочел, что это способ обращения к свойствам и методам родителя. В статье такой пример есть:

class Animal {
  constructor(name) {
    this.name = name;
  }

  walk() {
    alert("I walk: " + this.name);
  }
}

class Rabbit extends Animal {
  walk() {
    super.walk();
    alert("...and jump!");
  }
}
new Rabbit("Вася").walk();
// I walk: Вася
// and jump!

так вот, в Песочнице я переименовал свойство walk() у Rabbit-a

class Rabbit extends Animal {
 jump() {
    super.walk();
    alert("...and jump!");
  }

и после этого alert "...and jump!" перестал выводиться. Я так и не разобрался почему. Это по сути уже свой метод Ребенка, в котором он вызывает метод родителя, и я могу его называть как хочу. Или я не так понял? И вообще, вызов конструктора Родителя через super мне не совсем понятен. В смысле синтаксис. Чтобы изменить/заменить какой-то аргумент класса я должен написать super(name) а чтобы изменить/заменить метод

super.walk() {
new details
}

Так?

IgorKulishov commented 6 years ago

Итак по порядку - ты задал два вопроса: .(А как указывается в первоисточнике (оффициал документации)

The super keyword is used to call corresponding methods of super class

с примером

class Lion extends Cat {
  speak() {
    super.speak();
    console.log(this.name + ' roars.');
  }
}

Однако на то мы и программеры что бы экспериментировать и проверять и "ломать" стереотипы. Я заметил в твоем примере отсутствует последняя закрывающая скобка я ее добавил и попробовал еще раз и вуаля работает (у меня) - попробуй еще раз и дай знать если все таки не работает:

class OtherRabbit extends Animal {
jump() {
   super.walk();
   alert("...and quick jump!");
 }
} //эта скобка 
new OtherRabbit("Вася").jump();

Если это так, то это означает ты абсолютно прав и как дочерний класс может менять название метода и вызывать родительский как угодно (спасибо - теперь тоже буду знать 👍 ).

Кроме того что касается статического метода то вот в этом объяснении приводится пример где как раз можно менять называние метода:

class Rectangle{
  constructor() {}
  static logNbSides() {
    return 'I have 4 sides';
  }
}

class Square extends Rectangle {
  constructor() {}
  static logDescription() {
    return super.logNbSides() + ' which are all equal';
  }
}
Square.logDescription();
IgorKulishov commented 6 years ago

.(B super в constructor Разберем пример:

class Rectangle {
  constructor(height, width) {
    this.height = height;
    this.width = width;
    this.name = 'rectangle';
  }
  get area() {
    return this.height * this.width;
  }
}

class Cube extends Rectangle {
  //добавим constructor, поместим те же аргументы и кроме того добавим новый аргумент length 
  constructor(height, width, length) {
      // при помощи super мы передаем значения аргументов с constructor родитескому классу 
      super(height, width);
      //определим значение нового свойства length полученного из конструктора
      this.length = length;
      //очень просто переопределяем свойство name внутри нового образованного объекта
      this.name = 'cube';
  }
 // добавим новый метод (что бы использовать новое свойство length)
  cube() {
     // посмотрим на присвоение значения новому свойству length
     console.log('length : ', this.length);
     // посмотрим на присвоение значения свойству name
     console.log('name: ', this.name);
     // вернем значение куба используя все свойства класса (при помощи оператора return)
     return this.width * this.length * this.height;
  }
}

let cubeObject = new Cube(3, 4, 5 );
cubeObject.cube();

Как ты видешь при помощи super внутри constructor мы передаем значения аргументов конструктора значениям свойствам родительскон constructor-а. Кроме того как указано в статье, только после оператора super можно обращаться к свойствам класса и присваивать значения при помощи слова this (например this.height, this.width). Т.е. твой пример идет в сочетании с конструктором:

constructor(name) {
  super(name)
}

А что бы изменить/заменить метод:

class Rabbit extends Animal {
  newWalk() {
    super.walk();
    alert("...and jump!");
  }
}
g1er commented 6 years ago

Насчет вопроса А. Я нашел причину нестыковки. Дело было не в скобке, а в том, что изменив имя метода в конструкторе Ребенка, я вызывал метод под его старым названием

new Rabbit("Вася").walk();
//вместо
new Rabbit("Вася").jump();

По вопросу Б. 1) ты в примере в самом начале в конструкторе указываешь this.name = "rectangle", хотя в аргументах нет name. Получается, что можно в конструкторе определять аргументы, которые не обозначены в скобках? 2) В конструкторе Ребенка ты указываешь super(height, width). Но ведь они вместе со значениями итак наследуются от Родителя. Или это для того, чтобы менять значения length и width? Будет ли правильным написать обращение к этим параметрам и изменение их значений так:

class Cube extends Rectangle {
  constructor(length) {
  super(height) = 20;
  super(width) = 3;
  this.length = length;
 }
}
IgorKulishov commented 6 years ago

По вопросу Б.

  1. Получается, что можно в конструкторе определять аргументы, которые не обозначены в скобках?

Да, совершенно верно. В таком случае свойство нового обьекта будет заранее определено при создании нового обьекта.

Внутри constructor мы указываем аргументы которые будут переданы классу при создания нового объекта:

class Human {
  constructor(height, weight) {
    this.height = height;
    this.weight = weight;
    this.species = 'human';
  }
}
let human = new Human(180, 85);
console.log(human.species);

Как видно: 1) аргументы передаваемые нового класса new Human(180, 85) расположены именно в той последовательности как они определены в constructor 2) значение свойства species задается внутри конструктора класса, таким образом предопределено заранее, и не передается через аргумент при создании нового объекта

IgorKulishov commented 6 years ago

По вопросу Б.

  1. super(20, 3) - передает конструктору (constructor) родительского класса значения 20 и 3 при создании нового объекта, именно в той последовательности в какой они расположены внутри конструктора в родительском класса:

    class Rectangle {
    constructor(height, width) {
    ...
    }
    }

    Таким образом в твоем примере надо будет расположить

    class Cube extends Rectangle {
    constructor(length) {
    // передаем в super значения height=20, width=3 внутрь super и далее конструктору родительского класса, 
    // аргументы расположены в той последовательности как они расположены внутри constructor родительского класса  
    super(20, 3);
    this.length = length;
    }
    }
    //length = 5
    new Cube(5)

    В примере выше а) значения height=20, width=3 предопределены заранее внутри Cube constructor, а length мы передаем при создании нового объекта new Cube(5). б) super(20, 3) передает родительсткому конструктору Rectangle 20 и 3 соответственно.

IgorKulishov commented 6 years ago

В вопросе 1. так же как и в вопросе 2. мы можем определять значения свойств новых объктов внутри классов. При этом вопрос 1 обсуждает создание объекта от родительского класса, в то время как вопрос 2 создание объектов от дочернего класса, который передает фиксированные значения родительскому классу внутри super(20, 3). Точно так же мы могли бы передать переменные значения передаваемые через constructor(weight, height) и далее super(weight, height).

g1er commented 6 years ago

Я что-то уже совсем запутался... Зачем нам, создавая дочерний класс от Родителя, свойства и методы которого Ребенок перенимает, отправлять этому же Родителю назад значения? В моем понимании вещей это Родитель передает все Ребенку, а не наоборот. Мне, видимо, надо более подробно изучить что такое Super, потому что я в его назначении запутался

IgorKulishov commented 6 years ago

Значения свойств нового объекта определяются в момент создания нового объекта от дочернего класса при помощи оператора new и аргументов внутри дочернего класса. Примеры:

 let cube = new Cube(20, 3, 5)

или

//если нам надо определить только length (то только один аргумент) как в примере выше , а width и height имеют постоянное значение 20 и 3 соответственно
let cube = new Cube (5) 

Прошу прощение я не определили этот новый объект "let cube" в явном виде вчера в примерах , возможно это запутало.


Я изменю пример выше и сделаю все параметры переменными, задающимися через аргумент класса Cube при создании нового объекта let cube:

class Cube extends Rectangle { constructor(width, height, length) { // передаем значения height, width родительскому классу при создании нового объета внутри super
super(width, height); // length это собственное свойство дочернего класса Rectangle и определяем его значение внутри this.length = length; } }

let cube = new Cube(20, 3, 5);

IgorKulishov commented 6 years ago

Ответ на твой вопрос: В момент создания нового объекта от дочернего класса, дочерний класс передает родительскому классу значения при помощи оператора super(width, height) или фиксированными значениями super(20, 3). Это способ обращения к родительскому классу (как бы дочерний класс вызывает родительской и передает ему значения аргументов, которые которые мы задаем внутри new Cube(20,3,5).

IgorKulishov commented 6 years ago

Зачем нам, создавая дочерний класс от Родителя, свойства и методы которого Ребенок перенимает, отправлять этому же Родителю назад значения?

То что я объснил выше это только один из способов унаследования родительских свойств и методов используя оператор extends. Рассмотрим еще один (поменяем еще раз код) и пробежим по правилам применения super. Лучше всего объяснять, ломая код и делая наоборот. Мне нравится твой подход к вещам.

Итак на самом деле надо понимать что это прежде всего синтаксис и правила. Логика может не всегда присутствовать.

Важно помнить следующее: а) когда ты унаследуешь классы используя оператор extends, то внутри нового класса обязательно должен присутствовать super б) super должен присутствовать внутри constructor только один раз в) super должен стоять вначале, до того как ты применяешь this г) можно использовать super двумя способами

super(width, height);
super();
this.width = width;
this.height = height;

Попробуем поменять код и применим второй вариант.

class Rectangle {
  constructor(height, width) {
    this.height = height;
    this.width  = width;
    this.name = 'rectangle';
  }
  get area() {
    return this.height * this.width;
  }
}

class Cube extends Rectangle {
    constructor(height, width, length) {
  // определим отдельно super(width) с одним параметром, поместим width 
  // хотя на самом на месте height в конструкторе родительского класса 
 //специально поместим width , что бы показать что это ошибка и на самом деле он будет передан как height 
 // т.к. в конструкторе родительского класса первый аргемент это height и второй width, таким образом height будет определен, а width останется undefined значение:
  super(width);
        this.height = height;
      // что бы не допустить ошибку мы могли бы еще раз переопределим значение:  this.width  = width;
    // однако в данном примере мы покажем что width будет не определен и метод area даст ошибку как NaN (not a number)
        this.length = length;
        this.name = 'rectangle';
     }
  }

let cubeObject = new Cube(3, 4, 5 );
console.log(cubeObject.area);
// ответ NaN

Как видешь в этом примере произошла ошибка, потому что важно не название, а последовательность аргументов в super. В родительском класса первый это height а второй это width В нашем примере width остается не определенным т.к. мы ничего не указали во втором аргменте внутри super, a height определили дважды через super и переопределили внутри дочернего еще раз. Ты можешь попробовать отдельно вывести значение cubeObject.height и cubeObject.width

IgorKulishov commented 6 years ago

В моем понимании вещей это Родитель передает все Ребенку, а не наоборот. Мне, видимо, надо более подробно изучить что такое Super, потому что я в его назначении запутался

Когда ты создаешь объект от дочернего класса, ты обращаешься к дочернему классу который в момент создания объекта, обращается к родительскому и в этот момент передает конструктору родительского класса значениях аргементов определенных в скобрах: new Cube(20,3,5)

Описание есть в статье которую мы читаем, есть так же на английском (как правило все программисты читают на английском справочники).

Если тебе все еще не понятно, то как правило (как делаю я), я запоминаю и далее в процессе применения различных примеров становится понятно как их применять.

g1er commented 6 years ago

В принципе теперь мне уже понятнее. По сути, на мой вопрос ответили твои строки

когда ты унаследуешь классы используя оператор extends, то внутри нового класса обязательно должен присутствовать super

и

Когда ты создаешь объект от дочернего класса, ты обращаешься к дочернему классу который в момент создания объекта, обращается к родительскому и в этот момент передает конструктору родительского класса значениях аргементов определенных в скобрах

Получается что-то вроде обратной последовательности при определении значений аргументов. Т.е. Ребенок наследует св-ва и методы от Родителя и наделяет ими Объект. А при создании Объекта его значения свойств идут обратно к Ребенку, и потом от него к Родителю. И роль передатчика от Ребенка к Родителю выполняет Super. Поэтому его присутствие и необходимо при наследовании в дочернем классе. Я правильно понимаю происходящее? А если мы создадим объект от дочернего класса и дадим определение свойствам, получится, что эти значения присвоятся и самому главному Родителю. Т.е. если мы выведем значения свойств Родителя, нам покажут те значения, которые были присвоены последним объектом, который создали от дочернего класса? А если посмотреть более глобально, то получится, что Родитель будет принимать все значения своих свойств от всех классов, сколько бы их ни было создано. Т.е. если мы создадим 100 классов, и от них 100 объектов, которые будут отправлять Родителю 100 значений, не заклинит ли Родителя в условиях мультизадачности? Я может не знаю каких-то нюансов, может на практике это в принципе не возможно, но мое любопытство довело меня до такого вот вопроса )

g1er commented 6 years ago

Такой пример написал для тренировки:

class Animal{
    constructor(name, height){
    this.name = name;
    this.height = height;
    }

    get whoIs(){
        console.log("The " + this.name + " is " + this.height + " sm height.");
    }
}

class Rabbit extends Animal{
    constructor(name, height, speed){
        super(name, height);
        this.speed = speed;
    }

    get whoFast(){
        super.whoIs;
        console.log("And it's " + this.speed + " also.");
    }
}

let anyOne = new Rabbit("Bull", 180, "fast");
anyOne.whoFast
IgorKulishov commented 6 years ago

И роль передатчика от Ребенка к Родителю выполняет Super. Поэтому его присутствие и необходимо при наследовании в дочернем классе. Я правильно понимаю происходящее?

Да, роль передатчика значения свойств создаваемого объекта.

IgorKulishov commented 6 years ago

А если мы создадим объект от дочернего класса и дадим определение свойствам, получится, что эти значения присвоятся и самому главному Родителю. Т.е. если мы выведем значения свойств Родителя, нам покажут те значения, которые были присвоены последним объектом, который создали от дочернего класса?

Нет, значения присвоятся новому объекту, который будет иметь свойства и методы определенные в соответственно на основе дочернего и родительского классов. При этом сами классы остаются не затронутыми. Представь себе что классы это чертежи зданий, при этом объекты это здания построенные на основе чертежей. Существует оригинальный родительский общий чертеж любых зданий и модифицированный дочерний чертеж двух этажных зданий. При постройке двух этажных зданий ты смотришь чертежи соответственно проект двухэтажных зданий, но в определенных местах этот проект имеет сноски / ссылки на генеральный (родительский) проект.

IgorKulishov commented 6 years ago

А если посмотреть более глобально, то получится, что Родитель будет принимать все значения своих свойств от всех классов, сколько бы их ни было создано. Т.е. если мы создадим 100 классов, и от них 100 объектов, которые будут отправлять Родителю 100 значений, не заклинит ли Родителя в условиях мультизадачности? Я может не знаю каких-то нюансов, может на практике это в принципе не возможно, но мое любопытство довело меня до такого вот вопроса )

"не заклинит ли Родителя в условиях мультизадачности" Нет не заклинит, а) во-первых сам родительский класс только лишь шаблон / лекало / слепок для создания новых объектов. В твоем примере / вопросе будет создано 100 новых объектов путем 100-кратной генерации нового объекта при помощи оператора new и аргументов внутри скобок: new Cube(20,3,5). Т.о. сто раз будет использован / реплицирован класс Cube со значениями переданных аргументов, а дочерний класс Cube в свою очередь использует и передаст значения аргументов через super родительскому классу. б) во-вторых создание 100 объектов происходит последовательно - один за одним (представим себе 100 одинаковых строчек кода: new Cube(20,3,5); new Cube(7,40,10); ), так что одновременной использования класса не будет. При этом как я говорил, свойства самого класса остаются не использоваными / не измененными.

g1er commented 6 years ago

Нет, значения присвоятся новому объекту, который будет иметь свойства и методы определенные в соответственно на основе дочернего и родительского классов. При этом сами классы остаются не затронутыми.

Тогда у меня другой вопрос: зачем тогда в принципе передавать значения свойств Родителю, если они им никак не используются и, судя по всему, ни на что не влияют?

IgorKulishov commented 6 years ago

Именно так создаются новые объекты. Это такой синтаксис (соглашение). Указывается слово new перед классом объекта, внутрь класса передаются значения аргументов, которые задают значения внутреним свойствам нового объекта класса через constructor. Таким образом создается в памяти браузера новый объект. Сам класс при этом остается не измененным:

let myNewObject = new MyClass(arg1, arg2)

Именно так создаются новые объекты - это такой синтаксис (такое соглашение создания новых объектов на основе классов).

Т.е. важно понимать что сам браузер в котором выполняется программный код JavaScript сам написан на языке C++ (разработанный Microsoft) и поддерживает определенный синтаксис.

К примеру в JavaScript есть встроенные классы которые работают по тому же принципу. Пример:

// no agrs
let date = new Date();
console.log(date);
//with args
let otherDate = new Date(2013, 2, 1, 0, 70);
console.log(otherDate);

Источник документа