Skip to content

inheritance model

garevna edited this page Apr 5, 2019 · 26 revisions

🎓 Модель наследования JS

⤵️

Модель наследования JS основана на понятии прототипа

Прототип - это объект

Предшественником JS в плане прототипной модели наследования является язык

Стремление сделать синтаксис Javascript похожим на Java привело к появлению в языке таких "рудиментов", как ключевое слово new, лишенное практического смысла в языках с прототипной моделью наследования и создающее иллюзию наличия классов

Класс - это абстракция, объект - это воплощение

‼️ Уважаемые дамы и господа
Курение вредит вашему здоровью,
а курение конкретно в этом подъезде может вообще резко подорвать его

☕ Класс и объект

Чашка - это абстракция, но чашка, из которой ты в данный момент пьешь чай - это объект, т.е. конкретное воплощение абстракции "чашка"

Для создания конкретного экземпляра класса "чашка" в классической модели используется ключевое слово new


☕ Наследование в классической модели

Абстракция "чашка" вложена в другую абстракцию - "емкость", поскольку емкостью также является кастрюля, и цистерна, и колба

Таким образом, класс "чашка" наследует от класса "емкость"


Так вот:

в JS наследование происходит от объекта, а не класса,

т.е. от конкретного воплощения, а не от абстракции

Другими словами, JS - очень конкретный язык 😉


🎓 prototype

Для создания объекта в JS нам достаточно сделать следующее:

☕ 1

var sample = {
    name: "master"
}

При этом у нашего объекта "магическим образом" появляется свойство __proto__:

▼ { name: "master" }
    name: "master"
  ► __proto__: Object

Это свойство является ссылкой на объект prototype, который реально существует

внутри встроенного нативного объекта Object

На мой взгляд, это вполне соотносится с реальностью:

можно ли получить наследство от класса "дедушка" ? Даже кошке понятно, что только конкретный дедушка может оставить вам наследство

Таким образом, sample наследовал от Object

Чтобы посмотреть, что же унаследовал sample от Object, давайте развернем его свойство __proto__ в консоли:

▼ { name: "master" }
    name: "master"
  ► __proto__:
      ► constructor: ƒ Object()
      ► hasOwnProperty: ƒ hasOwnProperty()
      ► isPrototypeOf: ƒ isPrototypeOf()
      ► propertyIsEnumerable: ƒ propertyIsEnumerable()
      ► toLocaleString: ƒ toLocaleString()
      ► toString: ƒ toString()
      ► valueOf: ƒ valueOf()
      ► __defineGetter__: ƒ __defineGetter__()
      ► __defineSetter__: ƒ __defineSetter__()
      ► __lookupGetter__: ƒ __lookupGetter__()
      ► __lookupSetter__: ƒ __lookupSetter__()
      ► get __proto__: ƒ __proto__()
      ► set __proto__: ƒ __proto__()

Сам "дедушка" Object не является наследством

Логично, что наследство "дедушки" где-то хранится...

Оно хранится в свойстве prototype объекта Object

Проверим:

Object.prototype
▼ {constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, hasOwnProperty: ƒ, __lookupGetter__: ƒ, …}
  ► constructor: ƒ Object()
  ► hasOwnProperty: ƒ hasOwnProperty()
  ► isPrototypeOf: ƒ isPrototypeOf()
  ► propertyIsEnumerable: ƒ propertyIsEnumerable()
  ► toLocaleString: ƒ toLocaleString()
  ► toString: ƒ toString()
  ► valueOf: ƒ valueOf()
  ► __defineGetter__: ƒ __defineGetter__()
  ► __defineSetter__: ƒ __defineSetter__()
  ► __lookupGetter__: ƒ __lookupGetter__()
  ► __lookupSetter__: ƒ __lookupSetter__()
  ► get __proto__: ƒ __proto__()
  ► set __proto__: ƒ __proto__()

Например, "наследство" включает метод hasOwnProperty

Давайте попробуем его использовать:

sample.hasOwnProperty( "name" )   // true

О-па, мы получили true, т.е. метод работает!

Значит, наследство благополучно получено 😸

⁉️ зачем нам вообще нужно ключевое слово new ?

Итак, сделаем выводы:

  • "наследство" хранится в свойстве prototype "дедушки"
  • "наследник" получает свойство __proto__
  • свойство __proto__ является ссылкой на объект "наследство" ( prototype )

🎓 constructor

Теперь обратим внимание на первое, что мы видим в объекте prototype и в свойстве __proto__ экземпляра

Это свойство constructor

Значение у него ƒ Object()

Буковка ƒ говорит нам, что это функция

Object() говорит нам, что эта функция - Object

Итак, мы выяснили, что встроенный нативный объект Object:

  • является функцией
  • выступает в роли constructor

Таким образом, в объекте "наследства" ( prototype ) есть ссылка на "дедушку", от которого наследство получено

Кроме того, "дедушка" является функцией

И у "дедушки" вполне конкретная роль - он constructor

Теперь вопрос: а что сделало его "конструктором" ?


☕ 2

Проделаем следующий "финт":

console.dir ( function Sigma () {} )

и обратим внимание на наличие свойства prototype,

в котором есть свойство constructor

▼ ƒ Sigma()
    arguments: null
    caller: null
    length: 0
    name: "Sigma"
  ▼ prototype:
    ► constructor: ƒ Sigma()
    ► __proto__: Object
  ► __proto__: ƒ ()

Куда показывает свойство constructor ? - на функцию Sigma

То есть она по дефолту уже конструктор

У нее есть все, чтобы выступать в роли "дедушки"

Давайте именовать такие объекты "конструкторы", а не "дедушки" - это ближе к семантике языка

И опять обращаю ваше внимание на тот факт, что у нас все совершенно конкретно, нет никаких абстракций, никаких классов - только объекты ( функции тоже объекты )

Совершенно конкретная функция Sigma уже готова предоставить своим наследникам свой совершенно конкретный объект prototype в качестве наследства

Осталось только вызвать ее с ключевым словом new:

var obj = new Sigma

и мы получим наследника...

А можно ли без new передать наследство ?

function Sigma () {}
Sigma.prototype.say = function () {
    console.log ( "I'm the instance of Sigma: ", this instanceof Sigma )
}
var obj = {}
obj.__proto__ = Sigma.prototype

И у нас все в шоколаде!

Одна строчка:

obj.__proto__ = Sigma.prototype

сделала объект obj наследником Sigma

Причем оператор instanceof совершенно благопристойно возвращает нам true, как будто obj является экземпляром класса Sigma:

obj.say()
Результат в консоли:
I'm the instance of Sigma:  true

Но на самом деле все, что произошло - это помещение ссылки на объект prototype функции Sigma в свойство __proto__ объекта obj

Зачем же имитировать классовую модель, пряча "под капот" истинную природу наследования в JS ?

В этом примере мы столкнулись с еще одним важным явлением - this


🎓 this

Итак, мы уже поняли, что любая функция в JS является конструктором по своей сути, поскольку имеет контейнер для "наследства" - свойство prototype

Но что еще мы знаем о функциях JS ?

Мы знаем, что в момент вызова у них появляется ссылка на контекст вызова - this

Вы заметили, что в примере выше внутри функции say использовано это ключевое слово ?

Сама функция say находится внутри объекта prototype функции Sigma

Давайте изменим функцию say, не трогая все остальное:

Sigma.prototype.say = function () {
    console.log ( "My name is ", this.name )
}

и добавим объекту obj свойство name:

obj.name = "Google"

Теперь вызовем метод say, который унаследовал объект obj:

obj.say()
Результат в консоли:
My name is  Google

Тыц, this.name оказался "Google" !

т.е. this является ссылкой на obj !

Это как?

Да все правильно и закономерно:

  • во-первых, метод say доступен объекту obj, потому что унаследован
  • а во-вторых, при вызове
obj.say()

мы конкретно указали контекст вызова

ферштейн? 😉


🎓 Цепочка прототипов

function SampleClass () {
    this.name = "master"
}

var sample = new SampleClass ()

в результате у экземпляра sample появляется цепочка прототипов:

▼ SampleClass { name: "master" }
    name: "master"
  ▼ __proto__:
      constructor: ƒ SampleClass()
    ► __proto__: Object

первое свойство __proto__ является ссылкой на объект, который содержит:

  • свойство constructor

    ссылка на функцию SampleClass

  • еще одно свойство __proto__

    ссылка на Object

Итак, наша цепочка прототипов состоит уже из двух звеньев

и опять никаких классов...

Так зачем нам ключевое слово new ?

Только для имитации классовой модели наследования, которой нет...

А что у нас в действительности есть ?


🎓 Что такое конструктор

Любая функция может быть вызвана с ключевым словом new

Вопрос в том, что происходт


🎓 Конструктор Object

⤴️ ⤵️

Прототипом всех нативных объектов является Object

У конструктора Object есть свойство prototype

Это "генокод" всех создаваемых объектов JS

Он передается им при "рождении"

Все свойства и методы, перечисленные в свойстве prototype конструктора Object, будут доступны в каждом экземпляре создаваемого нативного объекта JS


☕ 3

var master = { name: "master" }
var child = { name: "child" }
var obj = {}
obj.__proto__ = child
obj.__proto__.__proto__ = master

Экземпляр obj в консоли:

▼ {}
    ▼ __proto__: 
        name: "child"
      ▼ __proto__: 
          name: "master"
        ► __proto__: Object

Добавим метод setName объекту __proto__

obj.__proto__.setName = function ( val ) {
    this.name = val
}

и посмотрим на объект child ( который мы не трогали ) в консоли:

▼ { name: "child", setName: ƒ }
    name: "child"
  ▸ setName: ƒ ( val )
  ▸ __proto__: Object

Это свидетельствует о том, что свойство __proto__ экземпляра является ссылкой на объект прототипа

В обоих примерах легко отслеживается цепочка прототипов

Последним "звеном" в этой цепочке будет Object - "Адам" всех нативных объектов JS

Заканчивается цепочка прототипов null

Если возникнет необходимость создать объект без прототипа, можно проделать следующее:

var obj = Object.create( null )

или так:

var obj = {}
obj.__proto__ = null

Thus, свойство __proto__ есть у любого объекта, и это свойство - ссылка на объект

❓ Где будет находиться свойство crocodile после выполнения кода:

var obj = {}
obj.__proto__.crocodile = "crocodile"
var obj = {}

obj.valueOf ()
obj.toString ()

🎓 Статические свойства Object

⤴️ ⤵️

( собственные свойства объекта Object )

А вот свойства и методы объекта Object, которые не находятся в свойстве prototype, не наследуются экземплярами, и могут быть вызваны только как свойства и методы объекта Object:

Поскольку они не передаются экземплярам, их называют статическими

☕ 3

var obj = {
     name: "test",
     type: "sample"
}
obj.__proto__.crocodile = "crocodile"

Object.getOwnPropertyNames ( obj )
Object.keys ( obj )
...

Метод Object.getOwnPropertyNames возвращает массив всех собственных свойств объекта, переданного в качестве аргумента метода

(2) ["name", "type"]

Метод Object.keys возвращает массив всех собственных перечислимых свойств объекта, переданного в качестве аргумента метода

(2) ["name", "type"]

Выведите в консоль

Object.keys ( obj )
Object.getOwnPropertyNames ( obj )

Ни первый, ни второй метод не выведут свойство crocodile, потому что это свойство не собственное ( оно находится в прототипе, т.е. является унаследованным )

А вот если итерировать obj с помощью цикла for ... in, мы получим свойство crocodile

Это означает, что свойство, добавленное нами в прототип объекта, не является собственным, но является перечислимым

Object.__proto__

⤴️ ⤵️

Выведем в консоль свойство __proto__ конструктора Object

▼ __proto__: ƒ ()
    ► apply: ƒ ()
      arguments: (...)
    ► bind: ƒ ()
    ► call: ƒ ()
      caller: (...)
    ► constructor: ƒ ()
      length: 0
      name: ""
    ► toString: ƒ ()
    ► Symbol(Symbol.hasInstance): ƒ ()
    ► get arguments: ƒ ()
    ► set arguments: ƒ ()
    ► get caller: ƒ ()
    ► set caller: ƒ ()
    ► __proto__: Object

а теперь выведем в консоль свойство prototype конструктора Function

▼ prototype: ƒ ()
    ► apply: ƒ ()
      arguments: (...)
    ► bind: ƒ ()
    ► call: ƒ ()
      caller: (...)
    ► constructor: ƒ ()
      length: 0
      name: ""
    ► toString: ƒ ()
    ► Symbol(Symbol.hasInstance): ƒ ()
    ► get arguments: ƒ ()
    ► set arguments: ƒ ()
    ► get caller: ƒ ()
    ► set caller: ƒ ()
    ► __proto__: Object

Очевидно, что Object наследует от Function, что логично, поскольку Object - это конструктор, т.е. функция

console.dir ( Object.__proto__.constructor.name )
// Function

При этом объект Function.prototype наследует от Object

( свойство Function.prototype.__proto__ является ссылкой на Object.prototype )

ссылка на
Object.__proto__ Function.prototype
Function.prototype.__proto__ Object.prototype

⤴️

💼 Упражнения


🔗 MDN 🔗 MDN

© Irina H.Fylyppova 2018
Использование данных материалов или любой их части коммерческими школами ( курсами ) является нарушением авторских прав


Новая версия


1 2 3 4 5
6 7 8 9 10
11 12 13 14 15
16 17 18 19

Занятие 1

⤵️

Занятие 2

⤴️ ⤵️

Занятие 3

⤴️ ⤵️

Занятие 4

⤴️ ⤵️

Занятие 5

⤴️ ⤵️

Занятие 6

⤴️ ⤵️

Занятие 7

⤴️ ⤵️

Занятие 8

⤴️ ⤵️

Занятие 9

⤴️ ⤵️

Занятие 10

⤴️ ⤵️

Занятие 11

⤴️ ⤵️

Занятие 12

⤴️ ⤵️

Занятие 13

⤴️ ⤵️

Занятие 14

⤴️ ⤵️

Занятие 15

⤴️ ⤵️

Занятие 16

⤴️ ⤵️

Занятие 17

⤴️ ⤵️

Занятие 18

⤴️ ⤵️

Занятие 19

⤴️ ⤵️

⤴️

ico20 Дополнительно
dir-20 Справочная инфо

Clone this wiki locally