Конкатенативное наследование против наследования классов в javascript

Транспиляция в TypeScript

Ещё одной популярной системой, в которой используется транспиляция, является TypeScript. Это язык программирования, код на котором трансформируется в код на ECMAScript 5, понятный любому JS-движку. Он предлагает новый синтаксис для написания JS-приложений. Вот как реализовать класс на TypeScript:

Вот абстрактное синтаксическое дерево для этого кода.

Абстрактное синтаксическое дерево

TypeScript поддерживает наследование.

Вот что получится в результате транспиляции этого кода:

Как видите, перед нами опять ES5-код, в котором, помимо стандартных конструкций, имеются вызовы некоторых функций из библиотеки TypeScript. Возможности функции аналогичны тем, о которых мы говорили в самом начале этого материала.

Благодаря широкому распространению Babel и TypeScript, механизмы для объявления классов и организации наследования на основе классов превратились в стандартные средства структурирования JS-приложений. Это способствовало добавлению поддержки этих механизмов в браузеры.

Короткая история популярных JavaScript библиотек:

Сначала каждый писал свои собственные библиотеки, и открытое совместное использование не было большой проблемой. А потом появился Prototype. (Название — большой намёк). Prototype сделал своё дело, расширив встроенные прототипы делегатов, используя конкатенативное наследование.

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

Следующей по популярности библиотекой была jQuery. Большим претендентом на славу JQuery были JQuery плагины. Они работали, расширяя прототип делегата jQuery, используя конкатенативное наследование.

jQuery остаётся самой популярной библиотекой JavaScript, когда-либо созданной. С ОГРОМНЫМ запасом. ОГРОМНЫМ.

Это то, где вещи начинают путаться, и расширение класса начинает проникать в язык… Джон Резиг (автор jQuery) писал про Простое классическое наследование в JavaScript, и люди в действительности начинали пользоваться им, хотя сам Джон не думал, что это часть jQuery (потому что прототипное ОО делало ту же работу лучше).

Появились полупопулярные Java фреймворки, такие как ExtJS, первыми открывшие, вроде бы не совсем мейнстримное использование класса в JavaScript. Это был 2007. JavaScript было уже 12 лет, прежде чем довольно популярная библиотека начала предоставлять пользователям JS возможность классического наследования.

Три года спустя, на сцену ворвался Backbone и в нём был метод , который имитировал классическое наследование, включая все свои противные особенности такие как хрупкие объектные иерархии. Вот когда весь ад вырвался наружу.

Это не JavaScript. Я неожиданно снова оказался в Java-аду. Это одинокое, тёмное, страшное место, где любые быстрые движения могут привести к тому, что целые иерархии содрогнутся и рухнут в объединяющихся, жёстко связанных конвульсиях.

Это чудовища, из которых состоят исправления

Но, сквозь документацию Backbone, пробивается луч света:

// Луч свет в чреве у// зверя...var object = {};_.extend(object, Backbone.Events);object.on("alert", function(msg) {  alert("Triggered " + msg);});object.trigger("alert", "an event");

Наш старый друг, конкатенативное наследование, спасший ситуацию благодаря примеси.

Оказывается, если вы взглянете на любую нетривиальную библиотеку JavaScript достаточно внимательно, вы найдёте примеры конкатенации и делегирования. Для разработчиков JavaScript это настолько распространённое и автоматическое действие, что они даже не думают об этом как о наследовании, даже если оно предназначено для той же цели.

11. Наследование статических свойств

Как и обычные свойства, статические также наследуются, что показано в предыдущем пункте на примере метода .

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

Обращение к статическому свойству выполняется либо через имя функции, либо через в теле статического метода.

Операция присваивания свойств производного класса, которая создаёт одноимённое свойство, независимое от базового (например, переопределение метода), может приобрести разночтение относительно статического поля.

Рассмотрим следующий показательный пример.

При вызове метода , поскольку собственный метод у класса не реализован (то есть не переопределён), вызывается метод базового класса. Однако указывает на класс , что, собственно, правильно, ибо метод вызван объектом (в данном случае это функция) . В теле метода превращается в что приводит к созданию у класса своего статического поля . Поле базового класса обнулено не будет.

Перепишем пример на классах и размножим поле на два поля. К одному из них в теле статического метода будем обращаться через , а к другому через прямое обращение к базовому классу как . В последнем случае как раз-таки будет обнулено собственное свойство класса , класс получит собственное свойство (равное 0), а вернёт значение поля базового класса.

Прототип

  • Прототип (prototype) — это экземпляр рабочего объекта. Объекты наследуются напрямую от других объектов.
  • является ссылкой на свойство прототипа родительского объекта, например:
  • Свойство prototype принадлежит только функциям, в частности, функциям конструктора. Конструктор Object создает обертку объекта.
  • Свойства proto и prototype используются для создания цепочки наследования свойств между объектами, начиная с Object и Primitive Types.
  • можно использовать для создания объектов с его свойством proto, связанным со свойством prototype объекта, переданного в качестве аргумента .
  • Object — это базовая функция (конструктор). Корнем всего в JavaScript является Object, который на самом деле является функцией.

Object имеет свойство prototype, которое является базовым объектом для всех вещей в JavaScript, включая функции JavaScript.

Вызов родительских методов

В механизме наследования, разобранном выше, есть одно белое пятно. Это — конструктор.

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

С наследованием через — это очень просто.

Вызов конструктора родителя с теми же аргументами, что были переданы осуществляется так:

function Rabbit(..)  {
	...
	Rabbit.superclass.constructor.apply(this, arguments)

	...
}

Конечно же, аргументы можно поменять, благо apply дает возможность вызвать функцию с любыми параметрами вместо в примере.

Аналогично можно вызвать и любой другой метод родительского класса:

Rabbit.superclass.run.apply(this, ...)

В этих примерах везде в явном виде указано имя класса хотя можно бы попробовать указать , который должен указывать на , т.к объект принадлежит этому классу.

Если так поступить, то будет ошибка при цепочке наследования классов из 3 элементов типа -> -> .

Проиллюстрируем ее на примере:

function foo() {}
foo.prototype.identify = function() {
	return "I'm a foo";
}

function bar() {}
extend(bar, foo)
bar.prototype.identify = function() {
	return "I'm a bar and " +
	this.constructor.superclass.identify.apply(this, arguments);
}

function zot() {}
extend(zot, bar)
zot.prototype.identify = function() {
	return "I'm a zot and " +
	this.constructor.superclass.identify.apply(this, arguments);
}

f = new foo();

alert(f.identify()); // "I'm a foo"

b = new bar();

alert(b.identify()); // "I'm a bar and I'm a foo"

z = new zot();

alert(z.identify()); // stack overflow

Последний вызов приведет к ошибке «too much recursion» из-за того, что , к которому идет обращение в функции обращается к дочернему классу . В результате вызывает сама себя в бесконечной рекурсии.

Правильный способ заключается в явном обозначении класса, т.е …

Оператор instanceOf проверяет принадлежность объекта классу, проходя по цепочке его прототипов, и используя для сравнения свойство prototype.

Логику его работы можно описать так:

function instanceOf(object, constructor) {
   var o=object

   while (o.__proto__ != null) {
      if (o.__proto__ === constructor.prototype) return true
      o = o.__proto__
   }
   return false
 }

Поэтому при правильной структуре прототипов он всегда корректно работает.

У этого оператора есть неприятная особенность при использовании нескольких окон: в разных окнах объекты классов (окружение) разное, поэтому массив из одного окна(фрейма) не будет опознан как Array в другом фрейме.

Впрочем, такая ситуация возникает довольно редко.

Примеры

Object.create

В ECMAScript 5 представлен новый метод создания объектов: Object.create. Прототип создаваемого объекта указывается в первом аргументе этого метода:

Используя ключевое слово

С выходом ECMAScript 6 появился целый набор ключевых слов, реализующих классы. Они могут показаться знакомыми людям, изучавшим языки, основанные на классах, но есть существенные отличия. JavaScript был и остаётся прототипно-ориентированным языком. Новые ключевые слова: «», «», «», «» и «».

Производительность

Длительное время поиска свойств, располагающихся относительно высоко в цепочке прототипов, может негативно сказаться на производительности (performance), особенно в критических в этом смысле местах кода. Кроме того, попытка найти несуществующие свойства неизбежно приведёт к проверке на их наличие у всех объектов цепочки прототипов.

Кроме того, при циклическом переборе свойств объекта будет обработано каждое свойство, присутствующее в цепочке прототипов.

Если вам необходимо проверить, определено ли свойство у самого объекта, а не где-то в его цепочке прототипов, вы можете использовать метод , который все объекты наследуют от .

— единственная существующая в JavaScript возможность работать со свойствами, не затрагивая цепочку прототипов. 

Примечание: Для проверки существования свойства недостаточно проверять, эквивалентно ли оно . Свойство может вполне себе существовать, но при этом ему может быть присвоено значение .

Плохая практика: расширение базовых прототипов

Одной из частых ошибок является расширение или других базовых прототипов.

Такой подход называется monkey patching и нарушает принцип инкапсуляции. Несмотря на то, что ранее он использовался в таких широко распространённых фреймворках, как например, Prototype.js, в настоящее время не существует разумных причин для его использования, поскольку в данном случае встроенные типы «захламляются» дополнительной нестандартной функциональностью.

Единственным оправданием расширения базовых прототипов могут являться лишь полифилы — эмуляторы новой функциональности (например,  для не поддерживающих её реализаций языка в старых веб-браузерах.

 наследует от :

Важно:

  • Типы определяются в 
  • Для наследования используется 

prototype и Object.getPrototypeOf

Как уже упоминалось, JavaScript может запутать разработчиков на Java или C++, ведь в нём совершенно нет «нормальных» классов. Всё, что мы имеем — лишь объекты. Даже те «classes», которые мы имитировали в статье, тоже являются функциональными объектами.

Вы наверняка заметили, что у  есть особое свойство . Это свойство работает с оператором . Ссылка на объект-прототип копируется во внутреннее свойство нового объекта. Например, в этом случае , JavaScript (после создания объекта в памяти и до выполнения функции function ) устанавливает . Потом, при попытке доступа к свойству нового экземпляра объекта, JavaScript проверяет, принадлежит ли свойство непосредственно объекту. Если нет, то интерпретатор ищет в свойстве . Всё, что было определено в  в равной степени доступно и всем экземплярам данного объекта. При внесении изменений в  все эти изменения сразу же становятся доступными и всем экземплярам объекта.

 работает рекурсивно, то есть при вызове:

JavaScript на самом деле выполняет что-то подобное:

а когда вы делаете так:

JavaScript проверяет, есть ли у  свойство .
и если нет, то проверяет  
а если и там нет, то ищет в  и так далее.

Важно чётко понимать принципы работы прототипной модели наследования, прежде чем начинать писать сложный код с её использованием.При написании JavaScript-кода, использующего наследование, следует помнить о длине цепочек прототипов и стараться делать их как можно более короткими во избежание проблем с производительностью во время выполнения кода.
Расширять базовые прототипы следует исключительно для поддержания совместимости кода с отдельными «древними» реализациями JavaScript, — во всех прочих случаях это плохая практика

Написать класс ES2015

Интерфейс может быть смоделирован путем создания виртуального метода, который вызывает ошибку. После наследства функция должна быть переписана, чтобы избежать ошибок:

Как упоминалось ранее, этот метод зависит от наследства. Наследование нескольких классов, нам также нужно несколько наследство или смешивание.

Другой метод состоит в том, чтобы написать практическую функцию, которая проверяет класс после его определения. Этот пример можно найтиПодождите минутку, JavaScript и поддержите многократное наследование! Андре Джаммархи. См. «Проверка функций базового объекта.

Время изучения различных способов применения многократного наследования и смешивания. Все следующие стратегии проверки доступныГадость 。

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

Чтобы продемонстрировать, посмотрите на код ниже:

Вы можете создавать эти объектные прототипы и модифицированы во время выполнения. Сначала я пытался использовать классс :

Выше не работает, потому что метод классаНе может быть перечислена 。 На самом деле это означаетНе будет копировать метод из класса. Это также затрудняет создание функции, копируя метод из одной категории в другую. Мы можем, однако, вручную копировать каждый метод:

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

преимущество

Смешивание не может быть инициализировано

Недостаток

  • Нужна дополнительная линейка кода
  • Object.Assign () немного неловко
  • Удаление прототипа наследования и ES2015

Встроенный

С классом ES2015 вы можете переопределить объект, вернувшись к конструктивным объекту:

Мы можем использовать эту функцию для записи подклассов из нескольких классов объектов. должны знать о этоВсе еще не смешивая работу хорошо, поэтому я использую объект, вот:

из-заОтносится к классу (необычный метод) на фоне выше,Не будет скопировать 。 Вместо этого вам придется находиться в групповых полях и методах.ЧтобыЧтобы быть в состоянии применить это, как это:

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

Я думаю, что мы можем согласиться с последним более читаемым.

преимущество

Я думаю, его принцип работы?

Недостаток

  • Очень неловко
  • Из синтаксиса ES2015 нулевой эффективность
  • Злоупотребление классом ES2015

Функция рабочей фабрики класса

Этот метод определяет возможности класса с использованием выполнения JavaScript.

Во-первых, нам нужен базовый класс. В нашем примере,сКак базовый класс. Если вы хотите начать с самого начала, сработал пустой класс.

Далее мы должны создать заводскую функцию для возврата нового класса для класса расширения. Это передается в качестве параметра. Это смешаны:

Теперь мы можем пройти любой классФункция вернет новую комбинацию уровняМы переходим к функции и любому классу:

Мы можем применить различные смешивания через несколько категорий:

Вы также можете использоватьКак базовый класс:

преимущество

Скорее всего, понять, потому что вся информация находится в заявлении класса.

Недостаток

Создание классов во время выполнения может повлиять на производительность запуска и / или использование памяти

2. Типы и классы

В js все объекты имеют тип . Тем не менее, они могут иметь различную структуру, иначе говоря — состав свойств. Чтобы отличать «тип js» от «типа объекта» будем использовать для последнего обозначение «класс». Класс описывается через функцию, и такая функция называется функцией-конструктором, иногда просто конструктором. Имя функции-конструктора выступает именем класса, и соглашением предусмотрено, что оно начинается с прописной буквы. Экземпляры класса создаются через вызов .

Вывод консоли показывает, что тип объекта . Когда же мы выводим сам экземпляр класса, то обозначен он будет как . Также есть способ проверить, является ли объект экземпляром класса через оператор .

Наследование с цепочкой прототипов

Объекты в JavaScript — динамические «контейнеры», наполненные свойствами (называемыми собственными свойствами). Каждый объект содержит ссылку на свой объект-прототип.
При попытке получить доступ к какому-либо свойству объекта, свойство вначале ищется в самом объекте, затем в прототипе объекта, после чего в прототипе прототипа, и так далее. Поиск ведётся до тех пор, пока не найдено свойство с совпадающим именем или не достигнут конец цепочки прототипов.

При добавлении к объекту нового свойства, создаётся новое собственное свойство (own property). Единственным исключением из этого правила являются наследуемые свойства, имеющие .

JavaScript не имеет «методов» в смысле, принятом в классической модели ООП. В JavaScript любая функция может быть добавлена к объекту в виде его свойства. Унаследованная функция ведёт себя точно так же, как любое другое свойство объекта, в том числе и в плане «затенения свойств» (property shadowing), как показано в примере выше (в данном конкретном случае это форма переопределения метода — method overriding).

В области видимости унаследованной функции ссылка указывает на наследующий объект (на наследника), а не на прототип, в котором данная функция является собственным свойством.

Переопределение методов

При
необходимости, методы базовых классов можно переопределять в дочерних.
Например, переопределим метод getColor в классе Line:

         getColor() {
                   return '';
         }

И, если теперь
его вызвать:

console.log( l1.getColor() );

то в консоли
увидим строчку:

Если же
закомментировать этот метод в дочернем классе и повторить вызов, то увидим
просто слово red. То есть, вот
так, довольно просто можно переопределять методы базового класса. Однако, часто
нужно сохранить функциональность метода из базового класса и расширить ее в
дочернем. Для этого можно вызвать переопределяемый метод из базового класса,
используя специальный объект super:

         getColor() {
                   let color = super.getColor();
                   return '';
         }

Смотрите, мы
здесь сначала берем значение цвета через геттер базового класса, а, затем,
добавляем квадратные скобки. Это более гибкий подход, т.к. цвет может
возвращаться и в виде строки и в виде числа или в еще каком-либо другом виде. И
мы, благодаря обращению к методу базового класса, сохраняем эту
функциональность.

Однако, обратите
внимание, контекст super на базовый класс может легко
потеряться, если мы обратимся к нему, например, из анонимной функции:

         showColor() {
                   setTimeout(function() {
                            console.log( super.getColor() );
                   }, );
         }

Произойдет
ошибка. А вот так, все будет работать:

         showColor() {
                   console.log( super.getColor() );
         }

или, так:

         showColor() {
                   setTimeout(() => {
                            console.log( super.getColor() );
                   }, );
         }

Мы здесь
используем стрелочную функцию, а как известно, они «прозрачные» и не образуют
собственного контекста.

Расширение класса

Функции конструктора и классы можно расширять на новые планы объекта на основе родительского экземпляра. Это позволяет не повторять код для похожих объектов, для которых нужно просто добавить дополнительные или более детальные характеристики.

Новые функции конструктора можно создавать на основе родительского экземпляра с помощью метода . В примере ниже мы создадим более конкретный класс персонажа и присвоим ему свойства с помощью метода , а также добавим дополнительное свойство.

constructor.js

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

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

Для классов ES6 ключевое слово используется вместо для доступа к родительским функциям. Мы будем использовать для обозначения родительского класса.

class.js

Теперь мы можем точно так же создать новый экземпляр .

Распечатаем на консоли и посмотрим результат.

Результат практически такой же, но в конструкции класса прототип связан с родительским объектом, в данном случае .

Ниже приводится полное сравнение процесса инициализации, добавления методов и наследования между функцией конструктора и классом.

constructor.js

class.js

Хотя синтаксис различается, конечный результат для обоих методов практически одинаковый. Классы дают более краткий способ создания шаблонов объектов, а функции конструктора более точно описывают конкретную механику.

Поведение this при прототипном наследовании

Следующий момент
в этом примере, на который, возможно, кто-то из вас уже обратил внимание: на
какой из двух объектов (geom или rect) ссылается this при вызове
сеттера:

rect.nameGeom = "Прямоугольник";

Как мы с вами
говорили на предыдущих занятиях по JavaScript, указатель this является
динамическим и определяется контекстом вызова метода. В данном случае контекст
– это объект rect и именно на
него ссылается this при вызове сеттера из базового объекта geom. В результате,
состояние базового объекта не меняется, а в rect добавляется
свойство name:

И в этом легко
убедиться, если выполнить следующие строчки:

console.log(rect.nameGeom);
console.log(geom.nameGeom);

Как видите, для
объекта geom отображается имя
«фигура», тогда как для rect – имя
«Прямоугольник». Или, эту проверку можно визуализировать еще лучше, если
воспользоваться специальным методом:

obj.hasOwnProperty(key)

который
возвращает true, если ключ (key) принадлежит
объекту obj и false – в противном
случае. В нашем случае можно записать такую конструкцию:

for(let prop in rect)
         if( rect.hasOwnProperty(prop) ) 
                   console.log(prop + ": " + rectprop);

На выходе
увидим:

draw: function () …
name: Прямоугольник

То есть,
свойство name действительно
было создано в дочернем объекте rect.

Но здесь есть
один тонкий момент. Если проделать ту же операцию с объектами, а не
примитивными типами данных, например с sp и ep:

let geom = {
         name "фигура",
         sp {x , y },
         ep {x 100, y 20},
         get nameGeom() {return this.name; },
         set nameGeom(name) {this.name = name; },
         get coords() {
                   return this.sp.x, this.sp.y, this.ep.x, this.ep.y;
         },
         set coords(coords) {
                   this.sp.x = coords; this.sp.y = coords1;
                   this.ep.x = coords2; this.ep.y = coords3;
         }
};

то операция:

rect.coords = 1,2,3,4;

изменит
состояние базового объекта geom, оставив дочерний без изменений:

console.log(rect.coords);
console.log(geom.coords);

Дело в том, что
мы здесь обращаемся сначала к свойству sp, которое определено
в базовом объекте (и мы автоматически переходим к нему), а затем уже, меняем
его свойства x и y. То есть, мы не
перезаписываем объект целиком, а работаем с его отдельными свойствами. А если
переписать, то оно добавится в дочернем объекте:

         set coords(coords) {
                   this.sp = coords; this.sp = coords1;
                   this.ep = coords2; this.ep = coords3;
         }

Вот на это
следует обращать внимание.

Видео по теме

JavaScript ООП #1: Прототипное наследование, свойство __proto__

JavaScript ООП #2: Свойство prototype

JavaScript ООП #3: Базовые свойства Object, методы create, getPrototypeOf и setPrototypeOf

JavaScript ООП #4: Классы — class, методы и свойства, Class Expression

JavaScript ООП #5: Наследование классов, переопределение методов, функция super

JavaScript ООП #6: Статические методы и свойства классов

JavaScript ООП #7: Приватные методы и свойства, оператор instanceof

JavaScript ООП #8: Примеси (Mixins). Что это, где и для чего используются

JavaScript ООП #9: Блоки try/catch/finally, оператор throw, проброс исключений

Строки

Методы

К прототипу была добавлена пара удобных методов. Большинство из них служат просто устранения обходных путей с методом для получения того же результата:

Просто, но эффективно. Другой удобный метод был добавлен для создания повторяющейся строки:

Шаблонные литералы

Шаблонные литералы обеспечивают чистый способ для создания строк и интерполяции выражений. Возможно, вы уже знакомы с синтаксисом; он основан на знаке доллара и фигурных скобках . Шаблонные литералы заключаются в обратные кавычки. Краткий пример:

Как видно из кода выше, шаблонные литералы просто удобны для конкатенации по сравнению с ES5. Однако шаблонные литералы также можно использовать и для многострочных литералов. Имейте в виду, что пробельные символы (например, символ новой строки) являются частью строки:

Модули

Модули – это, конечно, долгожданное дополнение к языку JavaScript. Я думаю, что это главное, ради чего стоит копаться в ES6.

Сегодня любой серьезный JavaScript проект использует какой-либо тип модульной системы, возможно что-то вроде шаблона «открытый модуль» или более обширные форматы AMD или CommonJS. Тем не менее, браузеры не располагают каким-либо типом модульной системы. Вам всегда необходим либо этап сборки, либо загрузчик ваших модулей AMD или CommonJS. Инструменты для обработки этого включают в себя RequireJS, Browserify и Webpack.

Спецификация ES6 включает в себя как новый синтаксис, так и механизм загрузчика модулей. Если вы хотите использовать модули и писать на будущее, то должны использовать данный синтаксис. Современные инструменты сборки поддерживают этот формат, возможно через плагин, поэтому всё должно пройти хорошо (не беспокойтесь, мы обсудим это далее в разделе «Транспиляция»).

А теперь о синтаксисе модулей ES6. Модули разработаны вокруг ключевых слов и . Рассмотрим пример с двумя модулями сразу:

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

Выражение в этом примере использует синтаксис (подобный деструктуризации), чтобы явно определить, что будет импортировано. Чтобы импортировать модуль целиком, должен быть использован символ в сочетании с ключевым словом, чтобы дать модулю локальное имя:

В модульной системе есть экспорт , который может быть функцией. Чтобы импортировать это значение по умолчанию в модуле, вам необходимо только предоставить локальное имя (т.е. без деструктурирования):

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

Имитация классов с помощью прототипов

Объект и его прототип

В JavaScript каждый объект связан с ещё одним объектом — со своим прототипом. Когда вы пытаетесь обратиться к свойству или методу объекта, поиск того, что вам нужно, сначала выполняется в самом объекте. Если поиск не увенчался успехом, он продолжается в прототипе объекта.

Рассмотрим простой пример, в котором описана функция-конструктор для базового класса :

Здесь мы назначаем функцию методом прототипа, так как нам надо, чтобы каждый экземпляр класса мог бы этим методом воспользоваться. Когда, в любом экземпляре , вызывают метод , его поиск начинается в самом объекте, для которого он вызван. Затем поиск продолжается в прототипе, где система и находит этот метод.

Прототип и два экземпляра класса Component

Попытаемся теперь расширить класс . Создадим конструктор нового класса — :

Если нам надо, чтобы класс расширял бы функционал класса и имел бы возможность вызывать его метод , нам нужно изменить его прототип. Когда метод вызывается для экземпляра дочернего класса, искать его в пустом прототипе бессмысленно. Нам нужно, чтобы, в ходе поиска этого метода, он был бы обнаружен в классе . Поэтому нам нужно сделать следующее:

Расширение возможностей класса Component с помощью класса InputField

Однако, вышеописанных действий недостаточно для реализации механизма, подобного традиционному наследованию. Каждый раз, когда мы расширяем класс, нам нужно выполнить следующие действия:

  • Сделать прототип класса-потомка экземпляром родительского класса.
  • Вызвать, в конструкторе класса-потомка, конструктор родительского класса для обеспечения правильной инициализации родительского класса.
  • Предусмотреть механизм вызова методов родительского класса в ситуациях, когда класс-потомок переопределяет родительский метод, но возникает необходимость в вызове исходной реализации этого метода из родительского класса.

Как видите, если JS-разработчик хочет пользоваться возможностями наследования, основанного на классах, ему постоянно придётся выполнять вышеописанные действия. В том случае, если нужно создавать множество классов, всё это можно оформить в виде функций, подходящих для многократного использования.

На самом деле, задача организации наследования, основанного на классах, изначально решалась в практике JS-разработки именно так. В частности, с помощью различных библиотек. Подобные решения стали весьма популярными, что недвусмысленно указывало на то, что в JavaScript чего-то явно не хватает. Именно поэтому в ECMAScript 2015 были представлены новые синтаксические конструкции, направленные на поддержку работы с классами и на реализацию соответствующих механизмов наследования.

10. Class вместо function

В js поддерживается такая языковая конструкция как класс. Перепишем наш пример с их использованием.

Реализация классов с помощью конструкции оставляет за бортом возню с прототипами

Не будем заостряться на синтаксисе, ибо он не из ряда вон выходящий, но обратим внимание на то, что прототипная архитектура функций осталась прежней. Сам класс при этом остаётся по сути и за исключением каких-то нюансов той же функцией

Заметим, что прототип производного класса равен базовому: . При использовании конструкции такая связь устанавливалась через метод . Эта связь позволяет производному классу вызывать статические методы базового класса.

Какая разница между наследованием через классы и прототипы?

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

Классы наследуются от классов и создают дочерние связи: иерархическую классовую таксономию

Классовое наследование: класс похож на шаблон – описание объекта который будет создан. Классы наследуются от классов и создают дочерние связи: иерархическую классовую таксономию.

Экземпляры обычно создаются через функции-конструкторы с помощью ключевого слова ‘new’. Классовое наследование может или не может использовать ключевое слово ‘class’ из ES6. Классы, какими вы знаете их из других языков на подобии Java, технически не доступны в JavaScript. Взамен их используются функции-конструкторы. Ключевое слово ‘class’ является упрощенной формой функции конструктора:

В JavaScript наследование через классы внедрено на верхнем уровне прототипного наследования, но это не значит что это тоже самое:

JavaScript наследование через классы использует цепочку прототипов для связывания ‘`Prototype`’ потомка с родительским ‘`Prototype`’ для передачи управления. Обычно, ‘super()’ конструктор также вызывается. Эти шаги формируют иерархию родитель/потомок с одним классом потомком и создает самую сильную связанность доступную в объектной-ориентированной архитектуре.

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

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

Экземпляры обычно создаются с помощью функция-конструкторов, объектные литералы или ‘Object.create()’.

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

Стрелочные функции

Стрелочные функции – отличное дополнение к языку JavaScript. Они создают короткий и лаконичный код. В этой статье мы рассказываем о стрелочных функциях пораньше, чтобы позже воспользоваться ими в других примерах. Следующий фрагмент кода показывает стрелочную функцию в сравнении аналогичной функцией, написанной в знакомом стиле ES5:

Если мы посмотрим на синтаксис стрелочных функций, то не увидим ключевого слова . Остается только ноль или более аргументов, «жирная стрелка» () и выражение функции. Возвращаемое выражение добавляется неявно.

При нуле или более одном аргументе, вы должны добавить круглые скобки:

Заключите выражение функции в блок , если вам необходима более сложная логика или больше пробельных символов (например, символ новой строки):

Стрелочные функции означают не только меньшее количество напечатанных символов, но их поведение отличается от обычных функций. Выражение стрелочной функции наследует и аргументы из окружающего контекста. Это означает, что вы можете избавиться от уродливых выражений, подобных , и вам не нужно связывать функции с правильным контекстом

Пример кода (обратите внимание на в сравнении с в версии ES5):

Рейтинг
( Пока оценок нет )
Editor
Editor/ автор статьи

Давно интересуюсь темой. Мне нравится писать о том, в чём разбираюсь.

Понравилась статья? Поделиться с друзьями:
Люкс-хост
Добавить комментарий

;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!: