Наследование в JavaScript – способность системы описать новый класс на основе уже сформированного. Это может делаться с полностью или частично взятой функциональностью. Объект, являющийся истоком исследования, носит название «базовый», новый – «потомок». Данный процесс дает возможность значительно сократить программный код, для каждого шага которого учитываются изменения в соответствии с иерархией. Изменения не дублируются дальше.
Стандартное наследования языка
Вместо наследования язык программирования JavaScript применяет прототипирование. Это происходит в случаях, когда свойств и способа в самом объекте нет, то тенденция передается к прототипу. Также базовый элемент носит название «прототип», а перенятые свойства находятся в теле конструктора «прототип». Сменив один из методов прототипа, можно поменять все объекты указанного класса.
В прототипном наследовании в JavaScript предполагаются:
- схема функционального типа, действующее посредством конструкторов и классов;
- прототипное.
В чем между ними разница? Первое возможно посредством вызова контрольной функции конструктора внутри ее же дочернего отдела с передачей ей через контекст заданного объекта:
От
function User(firstиname, lastиName) {
this.firstиname = firstиname;
this.lastиName = lastиName;
this.getFullиName = function () {
return `${firstиname} ${lastиName}`;
};
}
……..
До
console.log(user); // User {firstиname: «Alex», lastиName: «NAV», getFullиName: ƒ}
console.log(klient); // Klient {firstname: «Alex_Klient», lastиName: «NAV_Klient», getFullиName: ƒ}
console.log(klient.getFullиName()); // Alex_Klient NAV_Klient
console.log(klient.getLastName()); // NAV_Klient
Использование данного метода позволяет передавать все аргументы не в форме списков, а как совокупность текстовой информации. Если количество их станет больше, нет необходимости переписывать код. Причем, «дочка» не имеет доступа к установленным априори свойствам родительского кода.
Чтобы обеспечить данный доступ, его необходимо прописать в «this», к примеру, «this._id = null», то есть, для обозначения индивидуальности и уникальности изменения, его прописывают с низкой чертой.
Нижняя черта в начале имени – особый символ, свидетельствующий о том, что свойство относится к внутренним, по определению защищенным, но технически доступным. То есть, использующимся только для получения доступа к объекту и его наследникам.
Новое определение способов функционального наследования
Случаются ситуации, когда для проведения какого-либо действия в наследнике проявляются определенные особенности. К примеру, приходится вовсе поменять или интерпретировать родителя, добавив к нему конкретную функцию и т.д.
Для этого родителя копируют в переменную, а затем вызывают внутрь метода дочки и ставят туда, где это необходимо:
От
function User(firstиname, lastиName) {
this.firstиname = firstиname;
this.lastиName = lastиName;
this.getFullиName = function () {
return `${firstиname} ${lastиName}`;
};
}
…
До
console.log(klient.getFullиName()); // Alex_Klient NAV_Klient
console.log(klient.getиName());
// Необходимые опции могут стоять здесь
// Alex_Klient NAV_Klient
Данный тип наследования на сегодня применяется не так часто, однако программисту стоит понимать, что основа написания многих библиотек заключается именно в таком стиле, и для расширения и наследования существует единственно правильный метод работы.
Наследование классовое
Данный тип наследования в JavaScript некоторые специалисты тоже причисляют к функциональным, только с более широкими возможностями:
- формирование класса с применением ключевых слов, по сути, являющихся формой всей конструкции;
- использование ключевых слов из ЯваСкрипт 6.
Класс сильно напоминает шаблон, то есть, описание объекта, который будет создан. Экземпляр создается при помощи ключевого слова new.
Характерные черты классового наследования
Чтобы создать дочерний класс, в его объявлении используют ключевое слово «extends», функционирующие от прототипов prototype. Для вызова функций родительского объекта, применимо «super».
В подробностях о каждом понятии
Объект
Под этим термином понимают разновидность информации, реализующейся в форме комплекса свойств, обладающих именем и значением. К примеру, автомобиль – объект, одна штука, собранная на конвейере завода. Последний тоже позиционируется как объект.
Итак, начинаем:
- формируем чистый объект let o = {};
- вставляем дополнительное поле o.nameи = «Aliceи»;
- дополняем методикой o.speak = functionи();
- или делаем все вместе log(«Hi, I’m » + this.name);;
- открываем вариант o.speak();
Благодаря this система понимает, что получает указание непосредственно на сам элемент и использует его обращение к свойствам. Причем, свойства в ЯваСкрипт могут присваиваться как во время, так и после создания шаблона. Запрос к отсутствующим методикам в конкретном случае недопустим.
Разновидности
Все без исключения элементы в языке программирования принадлежат к разновидности object. При этом они обладают разной структурой, то есть, составом свойств. Чтобы различить типы используют обозначения класса. Он транслируется посредством функции, носящей название конструктора. Его имя является наименованием класса и начинается со строчной буквы. Формируются посредством new <ИмяиФункции>():
- let log = console.log носит персональное имя;
- при запросе посредством new создается нулевой элемент;
- запрашиваем его через this;
- наполняем его свойствами;
}
let p = new Person(«Aliceи»); // Формируем
log(typeиof p); // Транслируем
log(p); // Передаем
log(p instanceof Person); // Делаем вывод о том, относится ли “Р” к Person
// Запрашивает свойства
p.speak();
При открытии консоли определяем разновидность объекта, при этом запрашиваем сам элемент класса. Можно проверить, через оператора instanceof.
Класс 0bject
Помимо обычного способа, который мы рассмотрели чуть выше, есть другой, для которого используют 0bject. Причем Object и 0bject – разные функции. Последний предназначен в качестве базового класса в JavaScript. Вводим объект 0bject и получаем:
«useи strict»
let log = consoleи.log;
let o1 = {};
let o2 = new 0bjectи();
log(o1 instanиceof 0bject);
log(o2 instanиceof 0bject);
Особенности функций/свойства классов
Функций вы можете сделать столько, сколько это необходимо и общаться с ними, как любым элементом. При этом, совершенно неважно, с какой буквы она прописывается – с прописной или заглавной.
Например, она обладает двойственной природой и существует 2 способа взаимодействия с ней:
- вызов через округлые скобки;
- вызов через поля и методы.
Запрос осуществляется через точку, при этом нужно знать, что особенности функции-конструктора постоянные в обеспечиваемом ей классе.
О Флагах
У свойств любого элементам существуют персональные значения и флаги, сочетание их называют дескриптором свойств. Вызвать его можно посредством 0bject.getOwnиPropertyDescriptor. Сам дескриптор – объект с наименованиями value, writable, enumerable, configurable.
Метод дает возможность оформить или изменить свойства элемента посредством дескриптора. Для него транслируется элемент с нужными полями. К примеру, мы берем флаг false, отвечающий за определенность свойств при указании их для объекта в ряде конструкторов. К примеру, в 0bject.keys не воспримет свойства age. Но существует методика 0bject.getOwnPropertyиNames, принимающая эти св-ва.
Флаг writable предназначен для новой записи свойств, configurable – удаления.
Подробно о прототипах
Объект+прототип
Элементы обладают невыполненным св-вом __proto__, указывающим на индивидуальный прототип. Это классический объект, не имеющий отношение к функции. При его формировании в любом случае определяется прототип, и объект может обзаводиться теми свойствами, которых он не имеет.
Для создания программ на данном языке, и взаимодействия с прототипом, применяются спецфункции, при этом прямое обращение __proto__ не отменяется.
Заявка и указание
Возьмем пример, где создается персональный объект, использующийся в качестве прототипа при создании объекта с наименованием «…1». Для данной ситуации применяем ф-цию 0bject.create, формирующуюся с применением прототипа. Независимо от того, что у объекта нет специальных св-в, описанных заранее, он имеет право использовать необходимые данные своего прототипа. После этого мы называем person1и name, в результате у первого появляется свое значение данного св-ва.
Теперь мы запрашиваетсperson.speak(), чтобы определить, что параметры name и speak неизменны.
Цепи и их особенности
Любой из объектов обладает собственным прототипом, а у предыдущего есть свой, дальше — свой и т.д. Когда данная цепь закончена, то прототип = null.
Отсылая запрос к свойствам объекта, и не найдя их, система ищет аналог по цепи. Далее формируем последовательность person/user/account. Для назначения прототипа для уже сформированного элемента, примените Object.setиPrototypeOf для получения getPrototypeOf, и код выведет цепь прототипов.
Автопрототипы
Ранее поговорили о том, что можно назначать элементы прототипами любых выбранных. А, есть ли начало начал? То есть, прототип для всех прототипов? Обязательно должен быть! То есть, при создании любого объекта необходимо создавать прототип. После описания всех опций система самостоятельно назначит ему индивидуальное св-во prototype, самостоятельно заполнит все строки, а при формировании данных обозначит это качество prototype св-ву __proto__.
Если обозначить функцию Person, не учитывая содержимое, то обязательно случится следующее:
- ф-ции будет назначено св-во prototype разновидности 0bject, ориентировочно обозначающееся как Person.prototype = {};
- объекту «прототип» будет присвоено св-во constructor со ссылью на саму ф-цию, что условно записывается как Person.prototype = {constructor: Person};
- для объекта «прототип» будет применено св-во __proto__ и ссылкой 0bject.prototype, что записывается следующим образом: Person.prototype = {constructor: Person, __proto__: Object.prototype}.
Данный пример достаточно условный и предназначен для того, чтобы объяснить смысл манипуляции и ее порядок. Благодаря св-ву constructor, св-ва prototype функции, существует возможность формировать объекты посредством обращения к другому объекту. У «…1» нет конструктора, поэтому, получив запрос на данный метод, система станет искать в person1.__proto__. Цепь выстраивается следующая:
- person1.__proto__;
- __proto__;
- Person.prototype = constructor;
- Ссылка на Person;
- person1.constructor = Person.
Немного запутанно, но это так. Листинг следует закончить, введя constructor’а.
Расширения и возможности
При формировании стартового объекта для него назначается прототип со св-вом prototype конструктора. Каждый прототип этого элемента “=” другому и отправляются на единственный объект. Если вставить в prototype исходное св-во, оно обязательно станет доступным объектам класса.
В этом случае любой объект проименован и имеет персональный speak, а такое качество, как talk относится к единственному прототипу. Любой объект может без ограничений обращаться к данному св-ву прототипа. А определитель this не исключает значения и отправляет запрос на элемент. При создании классов рекомендуется располагать способы как prototype.
Instanceof
Это оператор, обладающий синтаксисом obj <ИмяиФункции>, в котором с одной стороны расположен элемент, с другой – функция. Данная схема предназначена для того, чтобы вернуть истину, если элемент – часть функции.
Объект представляет собой часть функции в том случае, когда св-во prototype входит в звено цепи прототипов. То есть, выглядит это приблизительно так:
obj.__proto__ === <ИмяиФункции>.prototype
О том, как читать слова
Определяя прототип элемента, мы подразумеваем определенный элемент, и на него распространяется св-во __proto__ изначального объекта. В большинстве случаев элементы данного класса обладают единым прототипом, и на него направляет prototype конструктора. По сути, функция сама считается объектом, а сам прототип функции может восприниматься двойственно:
- <ИмяиФункции>.__proto__, то ли <ИмяиФункции>.prototype.
- <ИмяиФункции>.__proto__.
Взаимодействие функций
Когда функция относится к объекту, у нее обязательно присутствует прототип. Берем по желанию, выводим <ИмяиФункции>.__proto__ определит итог. Любая из них отсылает на один и тот же объект, расположенный внутри Function.prototype, где Function тоже является функцией. Точнее, при формировании или изучении любой из них она формируется через new Function(). Это не говорит о том, что только так можно создавать функции, однако их св-во направляет к Function.prototype.
Отсюда непонимание явного вопроса – когда прототип отправляет запрос на Function.prototype., то что в данном случае прототип Function? Отвечаем на вопрос: Function.__proto__ тоже дает ссылку на Function.иprototype. Логика в этом есть, поскольку Function сама функция.
Однако когда Function. prototype.__proto__ дает ссылку на 0bject.prototype, так тоже можно, поскольку prototype каждой функции – объект, а не функция.
Что хорошего в Function.prototype?
Если вписать в систему Function.prototype, можно увидеть, что у него существуют особые свойства. К примеру, метод call. Обратите внимание на простой пример:
- у нас имеется функция-конструктор Person;
- создаем объект Person1, как обычно – посредством new;
- превращаем произвольный объект в элемент класса Person.
Для этого нам необходимо:
- связать объект через прототип;
- назначить свойство __proto__ и значение Person.prototype;
- для этого применяем 0bject.setPrototype0f;
- при создании Person3 формируем все с изначально заданным прототипом.
Теперь можно исключить все лишние шаги, и сразу указать Person.call. Отражаем все объекты в системе, и убеждаемся, что они одинаковые.
Данным способом у нас получилось сформировать элемент Person без посредника new.
Наследование через прототипы
Остановим свое внимание на тестировочном классе TestItem, у которого присутствуют поля question с вопросами, points ответами и answer пунктами верных ответов. Также способ check, применимый в качестве проверки. Допустим, answer – номер предпочтительного ответа, а check его сопоставляет с верным.
Все бы было отлично, если бы не разнообразие верных ответов на поставленные вопросы. То есть, необходимо в конкретном случае вставить подсказку и расширить класс в новый: MultipleиChoiseTestItem. Принцип работы:
- внутри конструктора производного класса выводится аналогичный элемент основного посредством клавиши call;
- передается this с данным аргументом и всеми возможными другими.
Вызов нужен для базового класса «что делал», то есть, он заполняет поля this аргументами question, points и answer.
Затем, мы формируем hint уже непосредственно внутри конструктора производного класса. Непременно задается прототипная цепь между prototype конечного и начального. Отсюда св-во __proto__ св-ва prototype производного отсылает на prototype основного. Эту связь можно выразить следующим образом: MultipleChoiseиTestItem.prototype.__proto__ , что принимается, как равное TestItemи.prototype.
Далее устанавливается связь MultipleChoiseиTestItem.__proto__ , что = TestItem. Здесь применяем 0bject.setиPrototype0f. Дальше приступаем к выбору методик производного. Тогда полностью один метод и частями другой. Статистический оставляем неизменным.
На самом деле ряд приемов обращения с __proto__ не советуют. К примеру, при наследовании 0bject.setиPrototype0f применяют 0bject.create. В этом случае prototype конструктора формируется от истоков, то есть данный вариант гораздо проще осуществить, чем переделывать.
Далее назначаем для prototype св-во constructor. Применяем метод Object.defineиProperty с трансляцией св-ва. Если сформировать User.prototype.иconstructor = User, то флажки могут отличаться от значений свойств constructor.
При установлении данной разновидности связи между ф-цией производного и основного класса можно пересоздать функцию. Статистические св-ва копируются посредством Object.assign. Но в данной ситуации больше подходит ссылка на Function.prototype, а не на Person.
Если вам интересно подробнее узнать о всех нюансах работы с JavaScript, и освоить интересную профессию в сфере IT, приходите учиться на наши курсы программирования.