Наследование и цепочка прототипов — JavaScript
В ECMAScript 5 представлен новый метод создания объектов: Object.create. Прототип создаваемого объекта указывается в первом аргументе этого метода:
Используя ключевое слово
class
С выходом ECMAScript 6 появился целый набор ключевых слов, реализующих классы. Они могут показаться знакомыми людям, изучавшим языки, основанные на классах, но есть существенные отличия. JavaScript был и остаётся прототипно-ориентированным языком. Новые ключевые слова: «class
«, «constructor
«, «static
«, «extends
» и «super
«.
"use strict";
class Polygon {
constructor(height, width) {
this.height = height;
this.width = width;
}
}
class Square extends Polygon {
constructor(sideLength) {
super(sideLength, sideLength);
}
get area() {
return this.height * this.width;
}
set sideLength(newLength) {
this.height = newLength;
this. width = newLength;
}
}
var square = new Square(2);
Производительность
Длительное время поиска свойств, располагающихся относительно высоко в цепочке прототипов, может негативно сказаться на производительности (performance), особенно в критических в этом смысле местах кода. Кроме того, попытка найти несуществующие свойства неизбежно приведёт к проверке на их наличие у всех объектов цепочки прототипов.
Кроме того, при циклическом переборе свойств объекта будет обработано каждое свойство, присутствующее в цепочке прототипов.
Если вам необходимо проверить, определено ли свойство у самого объекта, а не где-то в его цепочке прототипов, вы можете использовать метод
, который все объекты наследуют от Object.prototype
.
hasOwnProperty
— единственная существующая в JavaScript возможность работать со свойствами, не затрагивая цепочку прототипов.
undefined
. Свойство может вполне себе существовать, но при этом ему может быть присвоено значение undefined
.Плохая практика: расширение базовых прототипов
Одной из частых ошибок является расширение Object.prototype
или других базовых прототипов.
Такой подход называется monkey patching и нарушает принцип инкапсуляции. Несмотря на то, что ранее он использовался в таких широко распространенных фреймворках, как например, Prototype.js, в настоящее время не существует разумных причин для его использования, поскольку в данном случае встроенные типы «захламляются» дополнительной нестандартной функциональностью.
Единственным оправданием расширения базовых прототипов могут являться лишь полифиллы — эмуляторы новой функциональности (например, Array.forEach)
для не поддерживающих её реализаций языка в старых веб-браузерах.
B
наследует от A
:
function A(a){ this.varA = a; } A.
prototype = { varA : null, doSomething : function(){ } } function B(a, b){ A.call(this, a); this.varB = b; } B.prototype = Object.create(A.prototype, { varB : { value: null, enumerable: true, configurable: true, writable: true }, doSomething : { value: function(){ A.prototype.doSomething.apply(this, arguments); }, enumerable: true, configurable: true, writable: true } }); B.prototype.constructor = B; var b = new B(); b.doSomething();
Важно:
- Типы определяются в
.prototype
- Для наследования используется
Object.create()
prototype и Object.getPrototypeOf
Как уже упоминалось, JavaScript может запутать разработчиков на Java или C++, ведь в нём совершенно нет «нормальных» классов. Всё, что мы имеем — лишь объекты. Даже те «classes», которые мы имитировали в статье, тоже являются функциональными объектами.
Вы наверняка заметили, что у function A
есть особое свойство prototype
. Это свойство работает с оператором new
. Ссылка на объект-прототип копируется во внутреннее свойство
нового объекта. Например, в этом случае var a1 = new A()
, JavaScript (после создания объекта в памяти и до выполнения функции function A()
) устанавливает a1.[[Prototype]] = A.prototype
. Потом, при попытке доступа к свойству нового экземпляра объекта, JavaScript проверяет, принадлежит ли свойство непосредственно объекту. Если нет, то интерпретатор ищет в свойстве [[Prototype]]
. Всё, что было определено в prototype,
в равной степени доступно и всем экземплярам данного объекта. При внесении изменений в prototype
все эти изменения сразу же становятся доступными и всем экземплярам объекта.
[[Prototype]]
работает рекурсивно, то есть при вызове:
var o = new Foo();
JavaScript на самом деле выполняет что-то подобное:
var o = new Object();
o. [[Prototype]] = Foo.prototype;
Foo.call(o);
а когда вы делаете так:
o.someProp;
JavaScript проверяет, есть ли у o
свойство someProp
.
и если нет, то проверяет Object.getPrototypeOf(o).someProp
а если и там нет, то ищет в Object.getPrototypeOf(Object.getPrototypeOf(o)).someProp
Заключение
Важно чётко понимать принципы работы прототипной модели наследования, прежде чем начинать писать сложный код с её использованием.
При написании JavaScript-кода, использующего наследование, следует помнить о длине цепочек прототипов и стараться делать их как можно более короткими во избежание проблем с производительностью во время выполнения кода.
Расширять базовые прототипы следует исключительно для поддержания совместимости кода с отдельными «древними» реализациями JavaScript, — во всех прочих случаях это плохая практика.
Классическое Vs прототипное наследование Engine.
JSСуществует множество проблем с классическим наследованием, которые не существуют с прототипным наследованием, например:
Классическое наследование
Плотная муфта . Наследование – это самое узкое соединение, доступное в дизайне OO. В классах потомков есть глубокое знание их классов предков.
Негибкие иерархии (ака дупликация по необходимости) . Иерархии с одним родителем редко могут описывать все возможные варианты использования. В конце концов, все иерархии являются «неправильными» для новых применений – проблема, которая требует дублирования кода.
Множественное наследование сложно . Часто желательно наследовать более одного родителя. Этот процесс чрезмерно сложный, и его реализация несовместима с процессом одиночного наследования, что затрудняет его чтение и понимание.
Хрупкая архитектура . Из-за жесткой связи часто бывает сложно реорганизовать класс с «неправильным» дизайном, поскольку многие существующие функции зависят от существующего дизайна.
Задача Гориллы / Бананы . Часто есть части родителя, которые вы не хотите наследовать. Подкласс позволяет переопределять свойства родителя, но не позволяет вам выбирать, какие свойства вы хотите наследовать.
Прототипное наследование
Чтобы понять, как прототипное наследование решает эти проблемы, вы должны сначала понять, что существуют два разных типа прототипного наследования. JavaScript поддерживает оба:
Делегация . Если свойство не найдено в экземпляре, оно выполняется на прототипе экземпляра. Это позволяет вам обмениваться методами во многих случаях, предоставляя вам бесплатный образец мухи .
Конкатенация . Возможность динамически добавлять свойства к объекту позволяет вам свободно копировать любые свойства из одного объекта в другой, все вместе или выборочно.
Вы можете комбинировать обе формы прототипного наследования для достижения очень гибкой системы повторного использования кода.
Прототипное наследование позволяет использовать большинство важных функций, которые вы найдете на классических языках. В JavaScript закрытие и заводские функции позволяют реализовать частное состояние, а функциональное наследование можно легко комбинировать с прототипами, чтобы добавить микшины, которые поддерживают конфиденциальность данных.
Некоторые преимущества прототипного наследования:
Свободная связь . Экземпляру никогда не требуется прямая ссылка на родительский класс или прототип. Можно сохранить ссылку на прототип объекта, но это не рекомендуется, потому что это будет способствовать тесной связи в иерархии объектов – одной из самых больших ошибок классического наследования.
Плоские иерархии . Тривиально, когда прототипное ОО сохраняет иерархии наследования – используя конкатенацию и делегирование, вы можете иметь один уровень делегирования объектов и один экземпляр без ссылок на родительские классы.
Тривиальное множественное наследование . Наследование от нескольких предков так же просто, как объединение свойств нескольких прототипов с использованием конкатенации для создания нового объекта или нового делегата для нового объекта.
Гибкая архитектура . Поскольку вы можете наследовать выборочно с прототипом OO, вам не нужно беспокоиться о проблеме «неправильного дизайна». Новый класс может наследовать любую комбинацию свойств из любой комбинации исходных объектов. Из-за простоты выравнивания иерархии изменение в одном месте не обязательно вызывает рябь по длинной цепочке потомков.
Больше не горилл . Селективное наследование устраняет проблему бананов горилл.
Я не знаю, какое преимущество имеет классическое наследование над прототипным наследованием. Если кто-нибудь знает о любом, пожалуйста, просветите меня.
Два столпа JavaScript. Часть 1: наследование через прототипы.
Часть 1: наследование через прототипы
Перед тем как мы начнем — позвольте представиться. Во время чтения вы, вероятно, будете задаваться вопросом «кто он такой и что о себе воображает».
Меня зовут Эрик Элиот, я автор книги «Programming JavaScript Applications» (O’Reilly), ведущий документального фильма «Programming Literacy» и создатель серии платных онлайн-курсов «Learn JavaScript with Eric Elliott».
Внес свой вклад в создание ПО для Adobe Systems, Zumba Fitness, The Wall Street Journal, ESPN, BBC, топ-артистов таких как Usher, Frank Ocean, Metallica, и многих других.
Однажды…
Я блуждал во тьме. Я был слеп — топтался на месте, натыкался на предметы и ломал их, приводил в беспорядок всё к чему прикасался.
В 90-х я программировал на C++, Delphi и Java, создавал 3D-плагины для программы которая позже стала называться Maya (используется большинством крупных киностудий для создания блокбастеров).
И тут случилось это: пришествие Интернета. Все начали создавать веб-сайты, и, после практики создания и поддержки пары интернет-журналов, друг убедил меня, что будущее Интернета за SaaS (тогда этого термина не было и в помине). В то время я об этом не догадывался, однако этот общий настрой стал потихоньку влиять на мое общее представление о программировании, потому что если вы хотите делать хороший SaaS продукт — приходится использовать JavaScript.
И как только я начал его использовать — я уже не мог остановится. Внезапно, всё стало проще. Программы, которые я создавал, стали более гибкими. Код смог дольше существовать без необходимости рефакторинга. Вначале я думал о JavaScript как скриптовом языке для взаимодействия элементов страницы — однако, когда я узнал о cookies и AJAX, изначальное представление о нём изменилось.
JavaScript предлагает то чего не хватает в других языках:
Свободу!
JavaScript является одним из наиболее важных языков программирования всех времен не просто из-за своей популярности, а потому что он популяризует две чрезвычайно важные для развития всей науки программирования парадигмы:
- Наследование через прототипы (объекты не содержащие классов, делегирование прототипов более известное как OLOO — Objects Linking to Other Objects)
- Функциональное программирование (с помощью лямбда-выражений и замыканий)
Вместе я называю эти парадигмы двумя столпами JavaScript и, к моему стыду, они меня полностью совратили. Теперь мне не хочется программировать на языках в которых нет их реализации.
JavaScript навсегда запомнят как один из самых значимых языков когда-либо созданных. Множество других языков уже скопировали один, либо другой, либо оба этих столпа — они уже изменили способы написания приложений в других языках.
Брендан Айк (создатель JavaScript) специально не задумывал ни один из этих столпов, но JavaScript всей своей сущностью полагается на их использование. Оба они одинаково важны, но я обеспокоен тем, что большинство JavaScript-программистов полностью игнорируют одно или оба этих новшества, поскольку JavaScript отлично подходит для написания скверного кода, если вы не потрудились соответствующим образом его изучить.
Низкий порог вхождения — это важная особенность языка, которая позволяет быстро начать делать нужные вещи … но, строя карьеру разработчика, имейте ввиду что фаза игнорирования лучших практик языка не должна длиться больше года.
И если вы всё ещё находитесь в ней — пришло время перейти на новый уровень.
Если вы создаете конструкторы и наследуете их — вы так и не изучили JavaScript. И не важно, что вы занимаетесь этим с 1995 года. Вы не в состоянии воспользоваться основными преимуществами языка.
Вы работаете с липовым JavaScript, который существует только как надстройка над Java.
Пишете код на этом удивительном, меняющем правила игры, плодотворном языке и полностью упускаете то, что делает процесс таким замечательным.
Мы разводим бардак
«Не понимающие что идут в темноте никогда не выйдут на свет». ~ Брюс Ли
Конструкторы нарушают принцип открытости/закрытости. Создаете игру на HTML5? Хотите изменить стандартное поведение и использовать пул объектов вместо инстанцирования новых копий, чтобы сборщик мусора не влиял на FPS? Вы либо поломаете логику приложения, либо запутаетесь с костылями при создании фабрики методов.
Если вы вернете произвольный объект из конструктора — это сломает ссылки на прототип, и ключевое слово this
в конструкторе больше не будет ссылаться на только что созданный экземпляр объекта. Кроме того, «фабрика» получается менее гибкой, за счет того, что в ней не получится использовать this
; мы просто его выбрасываем.
А использовать конструкторы без строгого режима может быть просто опасно. Если вызывающий забывает использовать new
, ваш код не использует строгий режим или не является ES6-классом [вздох], все что вы присваиваете через this
будет засорять глобальное пространство имен. Не очень красиво, я считаю.
До появления строгого режима эта особенность языка вызывала труднонаходимые неявные ошибки (так было в паре стартапов, где я работал, в критические периоды роста, когда у нас было не так много времени чтобы гоняться за подобными ошибками).
В JavaScript фабричные методы — всего лишь конструктор функции минус обязательный new
, опасность засорения глобальной области видимости и неудобные ограничения (я имею ввиду раздражающую большую букву в начале названия класса).
На самом деле, JavaScript вообще не нуждается в конструкторах, поскольку любая функция может вернуть новый объект. С помощью динамического расширения объектов, синтаксиса объектов и Object.create()
у нас есть все что нужно. И наконец-то this
ведет себя везде одинаково. Ура-ура!
Добро пожаловать на седьмой круг ада
«Чаще всего я не настолько печален насколько должен». ~ Т. Х. Уайт
Каждый слышал аналогию про вареную лягушку: если вы поместите лягушку в кипящую воду — она выскочит. Если вы поместите лягушку в холодную воду и постепенно начнете подогревать — лягушка сварится, потому что не почувствует опасности. В нашей истории лягушки это мы.
Если поведение конструкторов это кастрюля с водой, то классическое наследование это не огонь, а пламя из седьмого круга ада Данте.
Проблема гориллы и банана
«Проблема объектно-ориентированных языков в том что они тащут за собой всю неявную среду. Вы хотите получить банан, но кроме банана в нагрузку получаете гориллу, и все чертовы джунгли!». ~ Джо Армстронг
Классическое наследование, как правило, позволяет наследовать только от одного предка, и это очень неудобный паттерн. Неудобный, потому что любой объектно-ориентированый паттерн наследования, который я видел в приложениях, в итоге был неправильным.
Допустим, вы начинаете с двумя классами: Инструменты и Оружие. Вы уже облажались — у вас не получится создать игру «Cluedo» (прим. пер.: имеется ввиду, что от класса Оружие нельзя с легкостью получить инстансы Нож и Револьвер, необходимые для игры.)
Проблема сильной связанности
Связь между классом и его родителем является самой крепкой формой зависимости в объектно-ориентированном дизайне (ООД). Переиспользуемый модульный код, наоборот, имеет слабые связи.
Внесение даже небольших изменений в класс создает наследуемые побочные эффекты, ломающие, казалось бы, совсем не связанные вещи.
Проблема необходимого дублирования
Очевидное решение вышеописанных проблем — вернуться назад во времени и создать новые классы с нужными изменениями и правильным наследованием … но, это связано с опасностью глобального рефакторинга. Обычно все заканчивается дублированием кода вместо переиспользования. Нарушается основополагающий принцип: не повторяйся.
Как следствие, созданные вами джунгли классов продолжают разрастаться и, по мере добавления уровней наследования, становятся все более запутанными. Когда вы находите баг — вы не правите его в одном месте. Вы правите его везде.
«Ой. Еще один». ~ Каждый классический ООП-программист рано или поздно
Это известная в ООП-кругах проблема необходимого дублирования.
Классы ES6 не исправляют ни одну из вышеописанных проблем. ES6 все усугубляет, потому что плохие идеи попадают в спецификации, и тиражируются в тысячах книг, статей в блогах.
В этом плане ключевое слово class
, вероятно, самое вредное нововведение в JavaScript. Я безмерно уважаю блестящих и трудолюбивых людей, которые прилагали свои усилиях по стандартизации, но даже блестящие люди иногда делают неправильные вещи. К примеру, попробуйте в консоли браузера выполнить .1 + .2
. (прим. пер.: тут автор явно троллит — вряд ли это архитектурная ошибка языка). Однако, это не мешает мне считать, что Брендан Айк внес большой вклад в веб, языки программирования и развитие ИТ в целом.
P.S.: Не используете super
если не получаете удовольствие от пошаговой отладки каждого слоя из множества абстракций наследований.
Возрождение
Эти проблемы множатся по мере роста приложения и, в конце концов, остается только переписать всё с нуля или полностью доломать — иногда это единственный способ сократить финансовые потери.
Я натыкался на это снова и снова, работа за работой, проект за проектом. Научимся ли мы когда-нибудь на своих ошибках?
В одной компании, где я работал, из-за подобных проблем пришлось даже перенести дату релиза на целый год. Я верю в доработки вместо переделок. Другая компания, которую я консультировал, чуть не обанкротилась по тем же самым причинам.
Это — не вопрос вкуса или стиля. Ваш выбор может как спасти, так и убить продукт.
Крупные компании, как правило, делают вид, что все под контролем. Но стартапы не могут позволить себе задержек из-за подобных проблем во время попыток найти свой продукт и рынок, потому что время всегда ограничено.
В современном коде я не видел ни одного успешного решения, которое помогало бы избежать всех вместе взятых проблем.
Шаг к свету
«Совершенство достигается не тогда, когда нечего больше добавить, а тогда когда больше нечего убавить». ~ Антуан де Сент-Экзюпери
Недавно, работая над библиотекой для демонстрации прототипного наследование для своей книги «Programming JavaScript Applications», я набрел на интересную идею: функция-фабрика, которая помогает создавать функции-фабрики, которые, в свою очередь, успешно наследуются и объединяются. Подобные фабрики я назвал «штампами», и библиотеку, соответственно, «Stampit». Она простая и очень маленькая. Я рассказывал о ней на конференции O’Reilly Fluent в 2013 году, и написал о статью в блоге.
Существует небольшое, но постепенно растущее, сообщество разработчиков, на чей стиль разработки повлияла эта библиотека. На текущий момент библиотека используется в продакшне множеством приложений с миллионами пользователей.
Конечно, Stampit не является единственной альтернативой. Например, Дуглас Крокфорд совсем не использует new
или this
, предлагая вместо этого полностью функциональный подход к повторному использованию кода.
Все его объекты являются набором функций, не зависимых от глобальных переменных, или содержат только данные (например, ассоциативные массивы). Это достаточно хорошо работает до тех пор пока вы не создадите сотни тысяч объектов и, вдруг, ваше приложение должно будет работать с минимальной задержкой (я говорю о игровых движках, обработчиках событий реального времени и т.п.). В этом случае передача управления вызывающим методам поможет оптимизировать управление ресурсами.
Еще одна хорошая альтернатива наследованию — использование модулей (я рекомендую npm + ES6 модули через Browserify или WebPack), или просто клонирование объектов через копирование их свойств (Object.assign()
, lodash.extend()
и т.п.).
Механизм копирования это еще одна форма прототипного наследования под названием «наследование через объединение».
Даже если вы последуете совету Дугласа Крокфорда и перестанете использовать this
, вы все еще можете использовать прототипы. Наследование через объединение возможно благодаря такому свойству JavaScript как динамическое расширение объекта: способности модифицировать экземпляр объекта после его создания.
Вам никогда не потребуются классы в JavaScript, и я ни разу не встречал ситуации в которой класс был бы лучше чем вышеописанные альтернативы. Если вы сможете придумать такую ситуацию — опишите её в комментариях (я предлагаю эту задачу на протяжении нескольких лет, и никто пока не придумал хороший пример использования кроме надуманных аргументов о микро-оптимизациях или стилевых предпочтениях).
Когда я говорю людям, что конструкторы и классическое наследование это зло — они начинают защищаться. Я не нападаю на вас. Я пытаюсь вам помочь.
Люди привязываются к одному стилю программирования, как будто способ разработки является частью их личности. Чушь.
Не имеет значения как это сделано, если сделано плохо.
Единственная важная вещь в разработке ПО — это то, что пользователи любят ваше ПО.
Многие люди не прислушиваются к советам, и предпочитают наступать на грабли самостоятельно. Не делайте эту ошибку, её цена может быть непомерной. У вас есть возможность учиться не наступать на грабли, на которые наступали многие снова, и снова в течении десятилетий. Целые книги посвящены этим граблям.
Одна из таких книг (вероятно, самая известная) «Паттерны проектирования» от GoF строится вокруг двух основополагающих принципов:
«Создавайте интерфейсы вместо реализации» (фокусируйтесь на том что делает ваш код вместо как он это делает) и «композиция предпочтительнее наследования».
Поскольку дочерние классы реализуют интерфейсы родительского — второй принцип вытекает из первого, но будет полезно обсудить его подробно.
Книга содержит целый раздел паттернов по созданию объектов, которые решают единственную цель — обойти ограничения конструкторов и наследования через классы.
Погуглите «new considered harmful», «inheritance considered harmful» и «super is a code smell». Вы найдете дюжину статей в авторитетных блогах наподобии Dr. Dobb’s Journal, написанных еще до изобретения JavaScript. Все они которых говорят одно и то же: new
, сломанное классическое наследование и связывание потомок-родитель (в нашем случае через super
) это дорога в один конец.
Даже Джеймс Гослинг, создатель Java, признает что Java неправильно реализует объекты.
Хотите ближе к JavaScript? Дуглас Крокфорд сообщил что Object.create()
был добавлен в синтаксис для того чтобы ему не пришлось использовать new
.
Кайл Симпсон (автор, «You don’t know JS») написал захватывающую серию из трех постов под названием «JS Objects: Inherited a Mess».
Кайл противопоставляет прототипное наследование классическому через классы, утверждая что первое проще и удобнее. Он даже ввел термин OLOO (Objects Linked to Other Objects), чтобы прояснить различия между делегированием прототипа и наследованием через класс.
Хороший код — простой код.
«Упрощение это удаление очевидного и добавление смысла». ~ Джон Маэда
Итак, если вы выбросите конструкторы с классическим наследовании из JavaScript кода, все станет:
- Проще (Легче читать и писать, исчезнут неправильные паттерны проектирования.)
- Более гибко (Переключиться с создания новых экземпляров на легко утилизируемые пулы объектов? Без проблем!)
- Мощнее и выразительнее (Наследовать от нескольких предков? Наследовать приватные методы? Как нефиг делать!)
Вариант получше
«Если вещь теоретически может быть опасной, и есть вариант получше — всегда используйте этот вариант». ~ Дуглас Крокфорд
Я не стараюсь отобрать у вас полезный инструмент. Моя задача — предупредить вас о правильном использовании нужных инструментов и при этом не выстрелить себе в ногу. В случае конструкторов и классов, есть лучшие варианты.
Другой распространенный аргумент, который программисты часто используют, звучит так: «код это способ самовыражения, а стиль программирования это искусство». Я считаю данный аргумент слишком эмоциональным и в целом нерациональным:
Ваш код не является продуктом вашего самовыражения, как и кисти художника не являются для него результатом работы. Код это инструмент. А программа это продукт.
Да, некоторые исходные коды сами по себе настолько красивы что кажутся искусством, однако если они не распечатаны на бумаге и не выставлены в картинной галерее — вряд ли их оценят искусствоведы. В большинстве же случаев, пользователи наслаждаются не вашим кодом, а результатом его работы — программой.
Хороший стиль программирования требует, чтобы, когда вы предстанете перед выбором: элегантный, простой гибкий код, или сложный, неудобный, с ограничениями — вы выберете первый вариант. Сейчас модно быть открытым новым особенностям языка, но есть два пути: правильный и не правильный.
Выберите правильный путь
~ Эрик Эллиот
прим. пер.: в статье много говорится о том, что классическое наследование сломано. Тут есть ловушка — легко представить что автор статьи говорит о наследовании в целом. Однако, не стоит забывать что наследование через классы это только один из возможных паттернов и существуют альтернативные варианты, которые, с этой точки зрения, работают как и задумано.
П.С. Не пропустите продолжение статьи «Два столпа JavaScript. Часть 2: Функциональное программирование». Или «Как прекратить заниматься микроменеджментом».
Типы наследования — урок. Биология, Общие биологические закономерности (9–11 класс).
В природе существуют два типа наследования нескольких генов: независимое и сцепленное.
Независимое наследование
Независимое наследование происходит, если гены, определяющие неаллельные признаки, расположены в разных парах хромосом. В этом случае наследование подчиняется третьему закону Менделя: происходит комбинирование генов и признаков во всех возможных сочетаниях. При анализирующем скрещивании дигетерозиготы появляются \(4\) варианта фенотипов в равных соотношениях.
Пример:
наследование признаков окраски и формы семян у гороха.
В результате скрещивания дигетерозиготных растений AaBb c рецессивными дигомозиготами aabb у потомства наблюдаются четыре фенотипа в одинаковых количествах.
Сцепленное наследование
Сцепленное наследование наблюдается, если гены, отвечающие за разные признаки, располагаются в одной паре гомологичных хромосом. Сцепление может быть полным или неполным.
При полном сцеплении гены, расположенные в одной хромосоме, наследуются вместе.
В этом случае скрещивание дигетерозиготы и рецессивной дигомозиготы приводит к появлению двух фенотипов, полностью повторяющих фенотипы родителей.
Дигетерозигота образует два вида гамет: и , а дигомозигота — один .
У потомства генотипы такие же, как у родителей: и — поэтому и фенотипы совпадают.
Пример:
скрещивание рецессивной дигомозиготной самки дрозофилы с дигетерозиготным самцом.
При скрещивании рецессивной по обоим признакам самки, имеющей тёмное тело и короткие крылья, с дигетерозиготным доминантным самцом образовалось \(50\) % серых мух с длинными крыльями и \(50\) % мух с тёмным телом и короткими крыльями.
Неполное сцепление генов наблюдается, если гены расположены в хромосоме далеко друг от друга. При скрещивании дигетерозиготы и рецессивной гомозиготы получается \(4\) класса различных фенотипов. При этом происходит образование новых генотипов, полностью отличающихся от родительских.
В этом случае в процесс образования гамет вмешивается кроссинговер.
Пример:
скрещивание дигетерозиготной самки дрозофилы с дигомозиготным самцом.
Если скрещивают дигибридную самку с гомозиготным рецессивным самцом, то в результате образуется потомство: \(41,5\) % — серых с длинными крыльями, \(41,5\) % — серых с короткими крыльями, \(8,5\) % — тёмных с длинными крыльями, \(8,5\) % — тёмных с короткими крыльями.
Установлено, что чем меньше расстояние между исследуемыми генами в родительской хромосоме, тем выше вероятность их полного сцепленного наследования. Соответственно, чем дальше друг от друга они располагаются, тем чаще происходит перекрест при мейозе.
Прототипное наследование в JavaScript | Кашиф Рошен
Object.setPrototypeOf () и Object.Create () пригодятся, когда нам нужно использовать прототипное наследование . Но оба эти метода относительно новы в JavaScript. Извечный механизм реализации прототипного наследования использует c онструкторские функции .
Что такое функции конструктора?
Функции конструктора — это обычные функции, которые инициализируют объект, когда они выполняются с оператором new
.Выполнение функции конструктора с ключевым словом new
называется вызовом конструктора .
Прежде чем мы продолжим, вам следует знать одну вещь о функциях.
Все функции JavaScript (кроме стрелочных) имеют встроенное свойство с именем prototype
. Не путайте это свойство с [[прототип]] .
Свойство прототипа
функции, , если не изменено , имеет единственное собственное свойство с именем конструктор
, которое указывает на расположение в памяти функции.
A c вызов инструктора приводит к трем причинам.
- Создан новый объект. Это может быть либо вновь созданный объект, либо альтернативный объект, возвращаемый функцией.
- Созданный объект устанавливается как контекст
этот
для этого конкретного вызова. - Свойство прототипа
Приведенный выше пример объединяет все концепции, которые мы обсуждали, для моделирования классического случая наследования .
Чтобы предоставить доступ к свойствам и методам Publication
, к новым экземплярам Book
, я передаю контекст this
внутри Book
в Publication
, используя Function.prototype.call () . Поскольку этот контекст
внутри Книги
относится к новому экземпляру, к нему добавляются свойства и методы Публикации
.
Поскольку экземпляры Book
также должны иметь возможность доступа к пользовательским методам, находящимся в [[prototype]] из Publication
, я использую объект .setPrototypeOf () — связывает [[прототип]] из автомобиля
и book
.
Мы можем использовать оператор instanceOf , чтобы проверить, появляется ли свойство prototype
функции конструктора где-нибудь в цепочке Prototype .
В JavaScript объекты могут наследовать от других объектов. У каждого объекта есть единственный объект-предок, и это приводит к цепочке взаимосвязанных объектов.Объекты в этой цепочке могут получить доступ к свойствам и методам объектов, которые предшествуют им. Object.setPrototypeOf () , Object.create () Функции конструктора и — это три способа, с помощью которых вы можете связывать объекты для достижения наследования .
- Классы и то, как они работают под капотом
- Конкатенативное наследование ( Object.assign () , Расширенный синтаксис и т. Д.)
- Функциональное наследование ( Заводские функции )
Увидимся скоро и оставайтесь в безопасности! 👋
🎉👨👩👧👧 Визуализация JavaScript: прототипное наследование
Вы когда-нибудь задумывались, почему мы можем использовать встроенные методы, такие как .length
, .split ()
, .join ()
для наших строк, массивов или объектов? Мы никогда явно не указывали их, откуда они берутся? Теперь не говорите: «Это JavaScript, лол, никто не знает, это волшебство», это на самом деле из-за того, что называется прототипным наследованием . Это довольно круто, и вы используете его чаще, чем думаете!
Нам часто приходится создавать множество однотипных объектов. Допустим, у нас есть веб-сайт, на котором люди могут просматривать собак!
Для каждой собаки нам нужен объект, который представляет эту собаку! 🐕 Вместо того, чтобы каждый раз писать новый объект, я буду использовать функцию-конструктор (я знаю, о чем вы думаете, я расскажу о классах ES6 позже!), Из которой мы можем создавать экземпляры Dog , используя новый
ключевое слово (этот пост на самом деле не об объяснении функций конструктора, поэтому я не буду слишком много об этом говорить).
У каждой собаки есть имя, порода, окрас и функция, по которой она лает!
Когда мы создавали функцию-конструктор Dog
, это был не единственный созданный нами объект. Автоматически мы также создали еще один объект, названный прототипом ! По умолчанию этот объект содержит свойство конструктора , которое является просто ссылкой на исходную функцию конструктора, в данном случае Dog
.
Свойство прототипа
в функции конструктора Dog не является перечислимым, что означает, что оно не отображается, когда мы пытаемся получить доступ к свойствам объекта.Но он все еще там!
Хорошо, так .. Почему у нас есть это свойство объект? Во-первых, давайте создадим собак, которых мы хотим показать. Для простоты я назову их dog1
и dog2
. dog1
— Дейзи, милый черный лабрадор! dog2
— это Джек, бесстрашный белый Джек Рассел 😎
Зарегистрируем dog1
в консоль и расширим ее свойства!
Мы видим добавленные свойства, такие как name
, порода
, цвет
и кора
.. но что это за свойство __proto__
! Он не перечислимый, что означает, что он обычно не отображается, когда мы пытаемся получить свойства объекта. Давайте расширим его! 😃
Ого, это выглядит точно так же, как объект Dog.prototype
! Угадайте, что, __proto__
— это ссылка на объект Dog.prototype
. Вот что такое прототипное наследование : каждый экземпляр конструктора имеет доступ к прототипу конструктора! 🤯
Так почему это круто? Иногда у нас есть свойства, общие для всех экземпляров.Например, функция bark
в этом случае: она одинакова для каждого экземпляра, зачем создавать новую функцию каждый раз, когда мы создаем новую собаку, каждый раз потребляя память? Вместо этого мы можем добавить его к объекту Dog.prototype
! 🥳
Каждый раз, когда мы пытаемся получить доступ к свойству в экземпляре, механизм сначала выполняет локальный поиск, чтобы узнать, определено ли свойство для самого объекта. Однако, если он не может найти свойство, к которому мы пытаемся получить доступ, движок проходит по цепочке прототипов через свойство __proto__
!
Теперь это всего лишь один шаг, но он может содержать несколько шагов! Если вы продолжили, то, возможно, заметили, что я не включил одно свойство, когда развернул объект __proto__
, показывающий Dog.прототип
. Dog.prototype
сам по себе является объектом, а это означает, что на самом деле он является экземпляром конструктора Object
! Это означает, что Dog.prototype
также содержит свойство __proto__
, которое является ссылкой на Object.prototype
!
Наконец, у нас есть ответ на вопрос, откуда берутся все встроенные методы: они находятся в цепочке прототипов! 😃
Например, метод .toString ()
. Он определен локально на объекте dog1
? Хм, нет.. Он определен на объекте dog1 .__ proto__
имеет ссылку, а именно Dog.prototype
? Тоже нет! Определен ли он для объекта Dog.prototype .__ proto__
имеет ссылку, а именно Object.prototype
? Да! 🙌🏼
Итак, мы только что использовали функции-конструкторы ( function Dog () {...}
), которые по-прежнему являются допустимым JavaScript. Однако в ES6 фактически введен более простой синтаксис для функций конструктора и работы с прототипами: классы!
Классы — это всего лишь синтаксического сахара для функций конструктора.Все по-прежнему работает так же!
Мы пишем классы с ключевым словом class
. Класс имеет функцию конструктора
, которая в основном является функцией конструктора, которую мы написали в синтаксисе ES5! Свойства, которые мы хотим добавить к прототипу, определены в самом теле класса.
Еще одна замечательная особенность классов — это то, что мы можем легко расширить других классов.
Допустим, мы хотим показать несколько собак одной породы, а именно чихуахуа! Чихуахуа — это (почему-то… 😐) все еще собака. Чтобы не усложнять этот пример, я пока передам классу Dog только свойство name
вместо name
, породы
и цвета
. Но эти чихуахуа тоже умеют делать что-то особенное, у них небольшой лай. Вместо того, чтобы сказать Гав!
, чихуахуа также может сказать Маленький гав!
🐕
В расширенном классе мы можем получить доступ к конструктору родительского класса с помощью ключевого слова super
. Аргументы, которые ожидает конструктор родительского класса, мы должны передать в super
: name
в этом случае.
myPet
имеет доступ как к Chihuahua.prototype
, так и к Dog.prototype
(и автоматически Object.prototype
, поскольку Dog.prototype
является объектом).
Поскольку Chihuahua.prototype
имеет функцию smallBark
, а Dog.prototype
имеет функцию bark
, мы можем получить доступ к smallBark
и bark
на myPet
!
Как вы понимаете, цепочка прототипов не может длиться вечно.В конце концов, есть объект, прототип которого равен null
: в данном случае объект Object.prototype
! Если мы попытаемся получить доступ к свойству, которое нигде не может быть найдено ни локально, ни в цепочке прототипов, возвращается undefined
.
Хотя я объяснил здесь все с помощью функций и классов конструкторов, другой способ добавления прототипов к объектам — это метод Object.create
. С помощью этого метода мы создаем новый объект и можем точно указать, каким должен быть прототип этого объекта! 💪🏼
Мы делаем это, передавая существующий объект в качестве аргумента объекту .создать метод
. Этот объект является прототипом объекта, который мы создаем!
Давайте зарегистрируем только что созданный объект me
.
Мы не добавляли никаких свойств к объекту me
, он просто содержит только неперечислимое свойство __proto__
! Свойство __proto__
содержит ссылку на объект, который мы определили как прототип: объект person
, который имеет имя
и свойство age
.Поскольку объект person
является объектом, значение свойства __proto__
в объекте person
равно Object.prototype
(но для облегчения чтения я не расширял это свойство в gif!)
Надеюсь, теперь вы понимаете, почему прототипное наследование является такой важной особенностью в чудесном мире JavaScript! Если у вас есть вопросы, не стесняйтесь обращаться ко мне! 😊
Демистифицируя классы ES6 и прототипное наследование — Скотч.io
На ранней стадии развития языка JavaScript возникло облако враждебности из-за отсутствия надлежащего синтаксиса для определения классов, как в большинстве объектно-ориентированных языков. Лишь после выпуска спецификации ES6
в 2015 году было введено ключевое слово class
; он описывается как синтаксический сахар по сравнению с существующим наследованием на основе прототипов.
На самом базовом уровне ключевое слово class
в ES6
эквивалентно определению функции конструктора, которое соответствует наследованию на основе прототипов.Может показаться излишним, что новое ключевое слово было введено для обертывания уже существующей функции, но оно приводит к читаемому коду и закладывает основу, на которой могут быть построены будущие объектно-ориентированные функции.
До ES6
, если бы нам нужно было создать план (класс) для создания множества объектов одного типа, мы использовали бы функцию конструктора, подобную этой:
function Animal (имя, яростный) {
Object.defineProperty (this, 'name', {
получить: функция () {возвращаемое имя; }
});
Объект.defineProperty (this, 'жестокий', {
получить: функция () {ожесточенный возврат; }
});
}
Animal.prototype.toString = function () {
return 'A' + '' + (this.fierce? 'fierce': 'tame') + '' + this.name;
}
Это простой конструктор объекта, который представляет собой схему для создания экземпляров класса Animal
. Мы определили два собственных свойства только для чтения и настраиваемый метод
toString
для функции конструктора. Теперь мы можем создать экземпляр Animal
с новым ключевым словом
:
var Lion = new Animal («Лев», правда);
консоль.журнал (Lion.toString ());
Отлично! Работает как положено. Мы можем переписать код, используя класс ES6
для краткой версии:
class Animal {
конструктор (имя, яростный) {
this._name = имя;
this._fierce = жестокий;
}
получить имя () {
вернуть this._name;
}
get fierce () {
return `Это животное $ {this._fierce? 'жестокий': 'приручить'} `;
}
нанизывать() {
return `Это $ {this._fierce? 'жестокий': 'приручить'} $ {this._name} `;
}
}
Давайте создадим экземпляр класса Animal
с ключевым словом new, как мы делали раньше:
let Lion = новое животное («Лев», истина);
консоль.журнал (Lion.fierce);
console.log (Lion.toString ())
Определение классов в ES6
очень просто и кажется более естественным в объектно-ориентированном смысле, чем предыдущая симуляция с использованием конструкторов объектов. Давайте подробно рассмотрим класс ES6
, изучив некоторые его атрибуты и ответвления.
Переход от использования старых конструкторов объектов к новым классам ES6
не должен быть трудным, поскольку ключевое слово class
- это просто специальная функция
и демонстрирует ожидаемое поведение функции.Например, как и функция, класс
может быть определен либо объявлением, либо выражением, где последнее может быть названо или безымянным.
Объявление класса определяется ключевым словом class
, за которым следует имя класса.
класс Животное {}
Мы уже использовали объявление class
, когда писали версию ES6
функции конструктора Animal
:
class Animal {
конструктор (имя, яростный) {
это._name = имя;
this._fierce = жестокий;
}
}
Выражение класса дает немного больше гибкости; класс может быть назван или безымянным, однако, когда выражение класса названо, атрибут name становится локальным свойством в теле класса, и к нему можно получить доступ с помощью свойства .name
.
Безымянное выражение класса пропускает имя после ключевого слова class
:
let animal = class {}
Выражение именованного класса, с другой стороны, включает имя:
let animal = class Animal {}
При сравнении конструктора объекта с классом
ES6
стоит отметить, что в отличие от конструктора объекта, к которому можно получить доступ до определения его области действия из-за подъема, классне может и не поднимается.
Хотя это может показаться серьезным ограничением для классов
ES6
, это не обязательно; Хорошая практикаES6
требует, чтобы, если какая-либо функция должна изменять экземпляр класса, она могла быть определена в любом месте программы, но должна вызываться только после того, как был определен сам класс.
После определения класса с помощью любого из двух указанных методов фигурные скобки {}
должны содержать члены класса, такие как переменные экземпляра, методы или конструктор; код в фигурных скобках составляет тело класса.
Конструктор класса - это просто метод, цель которого - инициализировать экземпляр этого класса. Это означает, что всякий раз, когда создается экземпляр класса, вызывается конструктор (где он определен) класса, чтобы что-то сделать с этим экземпляром; он может инициализировать свойства объекта с полученными параметрами или значениями по умолчанию, когда первые недоступны.
С классом может быть связан только один метод конструктора, поэтому будьте осторожны, чтобы не определять несколько методов конструктора, поскольку это приведет к ошибке SyntaxError.Ключевое слово super можно использовать в конструкторе объекта для вызова конструктора его суперкласса.
class Animal {
конструктор (имя, яростный) {
this._name = имя;
this._fierce = жестокий;
}
}
Код в теле класса выполняется в строгом режиме.
Определение методов
Тело класса обычно содержит переменные экземпляра для определения состояния экземпляра класса и методы прототипа для определения поведения экземпляра этого класса.До ES6
, если нам нужно было определить метод для функции-конструктора, мы могли бы сделать это так:
function Animal (имя, яростный) {
Object.defineProperty (this, 'name', {
получить: функция () {возвращаемое имя; }
});
Object.defineProperty (this, 'жестокий', {
получить: функция () {ожесточенный возврат; }
});
}
Animal.prototype.toString = function () {
return 'A' + '' + (this.fierce? 'fierce': 'tame') + '' + this.name;
}
или
function Animal (имя, свирепый) {
Объект.defineProperty (this, 'name', {
получить: функция () {возвращаемое имя; }
});
Object.defineProperty (this, 'жестокий', {
получить: функция () {ожесточенный возврат; }
});
this.toString = function () {
return 'A' + '' + (this.fierce? 'fierce': 'tame') + '' + this.name;
}
}
Два разных метода, которые мы определили выше, называются методами прототипа и могут быть вызваны экземпляром класса. В ES6 мы можем определить два типа методов: прототип и статические методы.Определение метода прототипа в ES6 очень похоже на то, что мы описали выше, за исключением того, что синтаксис чище (мы не включаем свойство prototype) и более читабелен:
class Animal {
конструктор (имя, яростный) {
this._name = имя;
this._fierce = жестокий;
}
получить имя () {
вернуть this._name;
}
get fierce () {
return `Это животное $ {this._fierce? 'жестокий': 'приручить'} `;
}
нанизывать() {
return `Это $ {this._fierce? 'свирепый': 'приручить'} $ {это._name} `;
}
}
Здесь мы сначала определяем два метода получения
с использованием более короткого синтаксиса, затем мы создаем метод toString
, который в основном проверяет, является ли экземпляр класса Animal
жестоким или прирученным животным. Эти методы могут быть вызваны любым экземпляром класса Animal
, но не самим классом.
Методы прототипа ES6
могут быть унаследованы дочерними классами для имитации объектно-ориентированного поведения в JavaScript, но под капотом функция наследования является просто функцией существующей цепочки прототипов, и мы очень скоро рассмотрим это.
Все методы
ES6
не могут работать как конструкторы и выдают ошибку TypeError, если они вызываются с ключевым словом new.
Статические методы напоминают методы-прототипы тем, что они определяют поведение вызывающего объекта, но отличаются от своих аналогов-прототипов, поскольку они не могут быть вызваны экземпляром класса. Статический метод может быть вызван только классом; попытка вызвать статический метод с экземпляром класса приведет к неожиданному поведению.
Статический метод должен быть определен с помощью ключевого слова static. В большинстве случаев статические методы используются в качестве служебных функций для классов.
Давайте определим статический служебный метод для класса Animal
, который просто возвращает список животных:
class Animal {
конструктор (имя, яростный) {
this._name = имя;
this._fierce = жестокий;
}
static animalExamples () {
return `Примеры животных: лев, слон, овца, носорог и т. д.
}
}
Теперь мы можем вызвать метод animalExamples ()
для самого класса:
консоль.журнал (Animal.animalExamples ());
В объектно-ориентированном программировании хорошей практикой является создание базового класса, который содержит некоторые общие методы и атрибуты, а затем создание других более конкретных классов, которые наследуют эти общие методы от базового класса и т. Д. В ES5
мы полагались на цепочку прототипов для моделирования этого поведения, и синтаксис иногда становился беспорядочным.
ES6
представил несколько знакомое ключевое слово extends
, которое упрощает наследование.Подкласс может легко наследовать атрибуты базового класса, например:
class Animal {
конструктор (имя, яростный) {
this._name = имя;
this._fierce = жестокий;
}
получить имя () {
вернуть this._name;
}
get fierce () {
return `Это животное $ {this._fierce? 'жестокий': 'приручить'} `;
}
нанизывать() {
return `Это $ {this._fierce? 'жестокий': 'приручить'} $ {this._name} `;
}
}
class Felidae extends Animal {
конструктор (имя, жестокость, семья) {
супер (имя, свирепый);
это._family = семья;
}
семья() {
return `A $ {this._name} - животное подсемейства $ {this._family} семейства $ {Felidae.name }`;
}
}
Мы создали здесь подкласс - Felidae (в просторечии - «кошки») - и он наследует методы класса Animal
. Мы используем ключевое слово super
в методе конструктора класса Felidae
для вызова конструктора суперкласса (базового класса). Замечательно, давайте попробуем создать экземпляр класса Felidae
и вызвать собственный метод и унаследованный метод:
var Tiger = new Felidae («Тигр», правда, «Пантеры»);
консоль.журнал (Tiger.toString ());
console.log (Tiger.family ());
Если в подклассе присутствует конструктор, он должен вызвать
super ()
перед использованием this.Также можно использовать ключевое слово
extends
для расширения функционального «класса», но попытка расширить класс, созданный исключительно из объектных литералов, приведет к ошибке.
В начале этой статьи мы увидели, что большинство новых ключевых слов в ES6
являются просто синтаксическим сахаром по сравнению с существующим наследованием на основе прототипов.Давайте теперь заглянем под листы и посмотрим, как работает цепочка прототипов.
Приятно определять классы и выполнять наследование с помощью новых ключевых слов ES6
, но еще лучше понимать, как все работает на каноническом уровне. Давайте посмотрим на объекты JavaScript: все объекты JavaScript имеют частное свойство, которое указывает на второй объект (за исключением нескольких редких случаев, когда он указывает на null
), связанный с ними, этот второй объект называется прототипом
.
Первый объект наследует свойства от объекта прототипа
, а прототип может, в свою очередь, унаследовать некоторые свойства от своего собственного прототипа, и так продолжается до тех пор, пока последний прототип в цепочке не получит свое свойство прототипа, равное null
.
Все объекты JavaScript, созданные путем присвоения идентификатора значению объектных литералов, используют один и тот же объект-прототип. Это означает, что их частное свойство прототипа указывает на тот же объект в цепочке прототипов и, следовательно, наследует его свойства.Этот объект может называться в коде JavaScript как Object.prototype
.
Объекты, созданные путем вызова конструктора класса или функции конструктора, инициализируют свой прототип из свойства prototype функции конструктора. Другими словами, когда новый объект создается путем вызова new Object ()
, прототип этого объекта становится Object.prototype
, как и любой объект, созданный из объектных литералов. Точно так же новый объект Date ()
будет унаследован от Date.prototype ()
и новый номер ()
из номера .prototype ()
.
Почти все объекты в JavaScript являются экземплярами
Object
, который находится на вершине цепочки прототипов.
Мы видели, что для объектов JavaScript нормально наследовать свойства от другого объекта (прототипа), но Object.prototype
демонстрирует редкое поведение, когда у него нет прототипа и не наследуются какие-либо свойства (он находится в верхней части цепочка прототипов) от другого объекта.
Почти все встроенные конструкторы JavaScript наследуются от Object.prototype
, поэтому мы можем сказать, что Number.prototype
наследует свойства от Object.prototype
. Эффект этого отношения: создание экземпляра Number
в JavaScript (с использованием new Number ()
) унаследует свойства как от Number.prototype
, так и от Object.prototype
, и это цепочка прототипов.
JavaScript можно рассматривать как контейнеры, поскольку они содержат определенные для них свойства, и эти свойства называются «собственными свойствами», но они не ограничиваются только своими собственными свойствами.Цепочка прототипов играет большую роль, когда ищется свойство объекта:
- Недвижимость сначала ищется по объекту как собственная собственность
- Если свойство не найдено в объекте, его прототип проверяется следующим
- Если свойство не существует в прототипе, запрашивается прототип прототипа.
- Этот запрос прототипа за прототипом продолжается до тех пор, пока свойство не будет найдено или пока не будет достигнут конец цепочки прототипов и не будет возвращена ошибка.
Давайте напишем код для четкой имитации поведения прототипного наследования в JavaScript.
Мы будем использовать метод ES5
- Object.create ()
- для этого примера давайте определим его:
Object.create () - это метод, который создает новый объект, используя его первый аргумент в качестве прототипа этого объекта.
let Животные = {};
Animals.eat = true;
let Cat = Object.create (Животные);
Cat.sound = true;
пусть Лев = Объект.создать (Кот);
Lion.roar = правда;
console.log (Lion.roar);
console.log (Lion.sound);
console.log (Lion.eat);
console.log (Lion.toString ());
Вот словесная интерпретация того, что мы сделали выше:
- Мы создали объект
Animal
, который наследует свойства отObject.prototype
- Мы инициализировали
Собственное свойство животных
- есть - в значение true (все животные едят) - Мы создали новый объект - Cat - и инициализировали его прототип как
Animal
(поэтому Cat наследует свойства отAnimal
иObject.прототип
) - Мы инициализировали
Собственное свойство кошки
- звук - в значение true (животные из семейства кошачьих издают звуки) - Мы создали новый объект - Lion - и инициализировали его прототип как
Cat
(поэтому Lion наследует свойства отCat
,Animal
иObject.prototype
) - Мы инициализировали
Собственное свойство Lion
- рев - в значение true (Lions can raw) - Наконец, мы зарегистрировали собственные и унаследованные свойства объекта
Lion
, и все они вернули правильные значения, сначала выполнив поиск свойств объектаLion
, а затем перейдя к прототипам (и прототипам прототипов), где этого не было. т доступны на бывшем.
Это базовая, но точная симуляция наследования прототипов в JavaScript с использованием цепочки прототипов.
В этой статье мы рассмотрели основы классов ES6 и прототипного наследования. Надеюсь, вы узнали кое-что из этой статьи. Если у вас есть вопросы, оставьте их ниже в разделе комментариев.
Понравилась эта статья? Подпишитесь на @neoighodaro в Twitter
Прототипное наследование JavaScript
Всего несколько недель до открытия 2021 JavaScript Full-Stack Bootcamp .
Записывайтесь в лист ожидания!
JavaScript является уникальным в мире популярных языков программирования из-за использования прототипного наследования.
В то время как большинство объектно-ориентированных языков используют модель наследования на основе классов, JavaScript основан на модели наследования прототипов .
Что это значит?
Каждый отдельный объект JavaScript имеет свойство, называемое прототипом
, которое указывает на другой объект.
Этот другой объект - прототип объекта .
Наш объект использует этот прототип объекта для наследования свойств и методов.
Допустим, у вас есть объект, созданный с использованием синтаксиса литерала объекта:
или созданный с помощью синтаксиса new Object
:
в любом случае прототип вагон
Объект
:
Если вы инициализируете массив, который является объектом:
const list = []
//или же
список констант = новый массив ()
прототип - Массив
.
Вы можете проверить это, проверив методы Object.getPrototypeOf ()
и Object.prototype.isPrototypeOf ()
:
const car = {}
const list = []
Object.getPrototypeOf (автомобиль) === Object.prototype
Object.prototype.isPrototypeOf (автомобиль)
Object.getPrototypeOf (список) === Array.prototype
Array.prototype.isPrototypeOf (список)
Все свойства и методы прототипа доступны объекту, имеющему этот прототип:
Объект.прототип
является базовым прототипом всех объектов:
Object.getPrototypeOf (Array.prototype) == Object.prototype
Если вам интересно, каков прототип Object.prototype, прототипа нет: это null
. Это особенная снежинка ❄️.
Приведенный выше пример является примером действующей цепи прототипов .
Я могу создать объект, расширяющий массив, и любой объект, который я создаю с его помощью, будет иметь массив и объект в своей цепочке прототипов и унаследовать свойства и методы от всех предков.
Помимо использования оператора new
для создания объекта или использования синтаксиса литералов для объектов и массивов, вы можете создать экземпляр объекта с помощью Object.create ()
.
Первым переданным аргументом является объект, используемый в качестве прототипа:
const car = Object.create ({})
const list = Object.create (массив)
Обратите внимание, потому что вы можете создать экземпляр массива, используя
const list = Object.create (Array.прототип)
, и в этом случае Array.isPrototypeOf (list)
- false, а Array.prototype.isPrototypeOf (list)
- true.
Учебный курс 2021 JavaScript Full-Stack Bootcamp начнется в конце марта 2021 года. Не упустите эту возможность, подпишитесь на лист ожидания!
Больше руководств по js:
- Чего следует избегать в JavaScript (плохие части)
- Отсрочки и обещания в JavaScript (+ Ember.js пример)
- Как загружать файлы на сервер с помощью JavaScript
- Стиль кодирования JavaScript
- Введение в массивы JavaScript
- Введение в язык программирования JavaScript
- Полное руководство по ECMAScript 2015-2019
- Понимание обещаний JavaScript
- Лексическая структура JavaScript
- Типы JavaScript
- Переменные JavaScript
- Список примеров идей веб-приложений
- Введение в функциональное программирование с помощью JavaScript
- Современный асинхронный JavaScript с асинхронностью и ожиданием
- Циклы и область действия JavaScript
- Структура данных JavaScript карты
- Заданная структура данных JavaScript
- Руководство по литералам шаблонов JavaScript
- Дорожная карта для изучения JavaScript
- Выражения JavaScript
- Откройте для себя таймеры JavaScript
- Объяснение событий JavaScript
- Циклы JavaScript
- Написание циклов JavaScript с использованием карты, фильтрации, сокращения и поиска
- Цикл событий JavaScript
- Функции JavaScript
- Глоссарий JavaScript
- Объяснение закрытий JavaScript
- Учебник по функциям стрелок в JavaScript
- Руководство по регулярным выражениям JavaScript
- Как проверить, содержит ли строка подстроку в JavaScript
- Как удалить элемент из массива в JavaScript
- Как глубоко клонировать объект JavaScript
- Введение в Unicode и UTF-8
- Юникод в JavaScript
- Как ввести первую букву строки в верхний регистр в JavaScript
- Как отформатировать число как денежное значение в JavaScript
- Как преобразовать строку в число в JavaScript
- это в JavaScript
- Как получить текущую метку времени в JavaScript
- Строгий режим JavaScript
- Выражения функции немедленного вызова JavaScript (IIFE)
- Как перенаправить на другую веб-страницу с помощью JavaScript
- Как удалить свойство из объекта JavaScript
- Как добавить элемент в массив в JavaScript
- Как проверить, не определено ли свойство объекта JavaScript
- Введение в модули ES
- Введение в CommonJS
- Асинхронное программирование JavaScript и обратные вызовы
- Как заменить все вхождения строки в JavaScript
- Краткое справочное руководство по синтаксису современного JavaScript
- Как обрезать ведущий ноль в числе в JavaScript
- Как проверить объект JavaScript
- Полное руководство по датам JavaScript
- Момент.js учебник
- Точка с запятой в JavaScript
- Арифметические операторы JavaScript
- Объект JavaScript Math
- Генерировать случайные и уникальные строки в JavaScript
- Как заставить ваши функции JavaScript "спать"
- Прототипное наследование JavaScript
- Исключения JavaScript
- Как использовать классы JavaScript
- Поваренная книга JavaScript
- Цитаты в JavaScript
- Как проверить адрес электронной почты в JavaScript
- Как получить уникальные свойства набора объектов в массиве JavaScript
- Как проверить, начинается ли строка с другой в JavaScript
- Как создать многострочную строку в JavaScript
- Руководство ES6
- Как получить текущий URL в JavaScript
- Руководство ES2016
- Как инициализировать новый массив значениями в JavaScript
- Руководство ES2017
- Руководство ES2018
- Как использовать Async и Await с массивом.prototype.map ()
- Async против кода синхронизации
- Как сгенерировать случайное число между двумя числами в JavaScript
- Учебное пособие по HTML Canvas API
- Как получить индекс итерации в цикле for-of в JavaScript
- Что такое одностраничное приложение?
- Введение в WebAssembly
- Введение в JSON
- Руководство JSONP
- Стоит ли использовать или изучать jQuery в 2020 году?
- Как скрыть элемент DOM с помощью простого JavaScript
- Как объединить два объекта в JavaScript
- Как очистить массив JavaScript
- Как закодировать URL-адрес с помощью JavaScript
- Как установить значения параметров по умолчанию в JavaScript
- Как отсортировать массив объектов по значению свойства в JavaScript
- Как подсчитать количество свойств в объекте JavaScript
- call () и apply () в JavaScript
- Введение в PeerJS, библиотеку WebRTC
- Работа с объектами и массивами с помощью Rest и Spread
- Разрушение объектов и массивов в JavaScript
- Полное руководство по отладке JavaScript
- Руководство по TypeScript
- Динамически выбрать метод объекта в JavaScript
- Передача undefined в JavaScript Выражения немедленно вызываемой функции
- Свободно типизированные языки против строго типизированных языков
- Как стилизовать элементы DOM с помощью JavaScript
- Приведение в JavaScript
- Учебное пособие по генераторам JavaScript
- Размер папки node_modules не является проблемой.Это привилегия
- Как устранить непредвиденную ошибку идентификатора при импорте модулей в JavaScript
- Как перечислить все методы объекта в JavaScript
- Метод String replace ()
- Метод String search ()
- Как я запускаю небольшие фрагменты кода JavaScript
- Руководство ES2019
- Метод String charAt ()
- Метод String charCodeAt ()
- Метод String codePointAt ()
- Метод String concat ()
- Метод String EndWith ()
- Строка включает метод ()
- Метод String indexOf ()
- Метод String lastIndexOf ()
- Метод String localeCompare ()
- Метод String match ()
- Метод String normalize ()
- Метод String padEnd ()
- Метод String padStart ()
- Метод String repeat ()
- Метод String slice ()
- Метод String split ()
- Метод String StartWith ()
- Метод String substring ()
- Метод String toLocaleLowerCase ()
- Метод String toLocaleUpperCase ()
- Метод String toLowerCase ()
- Метод String toString ()
- Метод String toUpperCase ()
- Метод String trim ()
- Метод String trimEnd ()
- Метод String trimStart ()
- Мемоизация в JavaScript
- Метод String valueOf ()
- Ссылка на JavaScript: String
- Метод Number isInteger ()
- Метод Number isNaN ()
- Метод Number isSafeInteger ()
- Метод Number parseFloat ()
- Метод Number parseInt ()
- Метод Number toString ()
- Метод Number valueOf ()
- Метод Number toPrecision ()
- Метод Number toExponential ()
- Метод Number toLocaleString ()
- Метод Number toFixed ()
- Метод Number isFinite ()
- Ссылка на JavaScript: номер
- Дескрипторы свойств JavaScript
- Метод Object assign ()
- Метод создания объекта ()
- Метод Object defineProperties ()
- Метод Object defineProperty ()
- Метод записи объекта ()
- Метод Object freeze ()
- Метод Object getOwnPropertyDescriptor ()
- Метод Object getOwnPropertyDescriptors ()
- Метод Object getOwnPropertyNames ()
- Метод Object getOwnPropertySymbols ()
- Метод Object getPrototypeOf ()
- Метод Object is ()
- Метод Object isExtensible ()
- Метод Object isFrozen ()
- Метод Object isSealed ()
- Метод Object keys ()
- Метод Object preventExtensions ()
- Метод уплотнения объекта ()
- Метод объекта setPrototypeOf ()
- Метод значений объекта ()
- Метод объекта hasOwnProperty ()
- Метод Object isPrototypeOf ()
- Метод Object propertyIsEnumerable ()
- Метод Object toLocaleString ()
- Метод объекта toString ()
- Метод объекта valueOf ()
- Ссылка на JavaScript: объект
- Оператор присваивания JavaScript
- Интернационализация JavaScript
- Тип JavaScript оператора
- Новый оператор JavaScript
- Операторы сравнения JavaScript
- Правила приоритета операторов JavaScript
- Экземпляр JavaScript оператора
- Заявления JavaScript
- Область действия JavaScript
- Преобразование типов JavaScript (приведение)
- Операторы равенства JavaScript
- Условное выражение if / else в JavaScript
- Условный переключатель JavaScript
- Оператор удаления JavaScript
- Параметры функции JavaScript
- Оператор распространения JavaScript
- Возвращаемые значения JavaScript
- Логические операторы JavaScript
- Тернарный оператор JavaScript
- Рекурсия JavaScript
- Свойства объекта JavaScript
- Объекты ошибок JavaScript
- Глобальный объект JavaScript
- Функция JavaScript filter ()
- Функция JavaScript map ()
- Функция JavaScript reduce ()
- Оператор in в JavaScript
- Операторы JavaScript
- Как получить значение свойства CSS в JavaScript
- Как добавить прослушиватель событий к нескольким элементам в JavaScript
- Поля частного класса JavaScript
- Как отсортировать массив по значению даты в JavaScript
- Поля открытого класса JavaScript
- Символы JavaScript
- Как использовать библиотеку JavaScript bcrypt
- Как переименовать поля при деструктуризации объекта
- Как проверять типы в JavaScript без использования TypeScript
- Как проверить, содержит ли массив JavaScript определенное значение
- Что означает оператор двойного отрицания !! делать в JavaScript?
- Какой оператор равенства следует использовать при сравнении JavaScript? == против ===
- Стоит ли изучать JavaScript?
- Как вернуть результат асинхронной функции в JavaScript
- Как проверить, пустой ли объект в JavaScript
- Как выйти из цикла for в JavaScript
- Как добавить элемент в массив по определенному индексу в JavaScript
- Почему не следует изменять прототип объекта JavaScript
- В чем разница между использованием let и var в JavaScript?
- Ссылки, используемые для активации функций JavaScript
- Как соединить две строки в JavaScript
- Как объединить два массива в JavaScript
- Как проверить, является ли значение JavaScript массивом?
- Как получить последний элемент массива в JavaScript?
- Как отправлять данные в кодировке с помощью Axios
- Как получить дату завтрашнего дня с помощью JavaScript
- Как получить вчерашнюю дату с помощью JavaScript
- Как получить название месяца из даты JavaScript
- Как проверить, совпадают ли две даты в один и тот же день в JavaScript
- Как проверить, относится ли дата к дню в прошлом в JavaScript
- Операторы, помеченные JavaScript
- Как дождаться разрешения 2 или более обещаний в JavaScript
- Как получить дни между двумя датами в JavaScript
- Как загрузить файл с помощью Fetch
- Как отформатировать дату в JavaScript
- Как перебирать свойства объекта в JavaScript
- Как рассчитать количество дней между двумя датами в JavaScript
- Как использовать ожидание верхнего уровня в модулях ES
- Динамический импорт JavaScript
- JavaScript Необязательная цепочка
- Как заменить пробел внутри строки в JavaScript
- Нулевое объединение JavaScript
- Как сгладить массив в JavaScript
- Это десятилетие в JavaScript
- Как отправить заголовок авторизации с помощью Axios
- Список ключевых и зарезервированных слов в JavaScript
- Как преобразовать массив в строку в JavaScript
- Как удалить все содержимое папок node_modules
- Как удалить дубликаты из массива JavaScript
- Let vs Const в JavaScript
- Один и тот же вызов POST API в различных библиотеках JavaScript
- Как получить первые n элементов в массиве в JS
- Как разделить массив на несколько равных частей в JS
- Как замедлить цикл в JavaScript
- Как загрузить изображение на холст HTML
- Как разрезать строку на слова в JavaScript
- Как разделить массив пополам в JavaScript
- Как написать текст на холсте HTML
- Как удалить последний символ строки в JavaScript
- Как удалить первый символ строки в JavaScript
- Как исправить ошибку TypeError: не удается назначить только для чтения свойство «exports» объекта «#
- Как создать всплывающее окно с намерением выхода
- Как проверить, является ли элемент потомком другого
- Как принудительно вводить учетные данные для каждого запроса Axios
- Как устранить ошибку "не функция" в JavaScript
- Гэтсби, как поменять фавикон
- Загрузка внешнего файла JS с помощью Gatsby
- Как определить темный режим с помощью JavaScript
- Посылка, как исправить ошибку `регенератор Время выполнения не определено`
- Как определить, используется ли блокировщик рекламы с JavaScript
- Деструктуризация объектов с типами в TypeScript
- Справочник Deno: краткое введение в Deno 🦕
- Как получить последний сегмент пути или URL-адреса с помощью JavaScript
- Как перемешать элементы в массиве JavaScript
- Как проверить, существует ли ключ в объекте JavaScript
- Возбуждение событий и захват событий
- событие.stopPropagation против event.preventDefault () против return false в событиях DOM
- Примитивные типы и объекты в JavaScript
- Как узнать, к какому типу относится значение в JavaScript?
- Как вернуть несколько значений из функции в JavaScript
- Стрелочные функции и обычные функции в JavaScript
- Как мы можем получить доступ к значению свойства объекта?
- В чем разница между null и undefined в JavaScript?
- В чем разница между методом и функцией?
- Как мы можем выйти из цикла в JavaScript?
- JavaScript для.петли
- Что такое деструктуризация объектов в JavaScript?
- Что поддерживает JavaScript?
- Как заменить запятые на точки с помощью JavaScript
- Важность тайминга при работе с DOM
- Как перевернуть массив JavaScript
- Как проверить, является ли значение числом в JavaScript
- Как принять неограниченное количество параметров в функции JavaScript
- Объекты прокси JavaScript
- Делегирование событий в браузере с использованием ванильного JavaScript
- Супер-ключевое слово JavaScript
- Введение в XState
- Значения передаются в JavaScript по ссылке или по значению?
- Пользовательские события в JavaScript
- Пользовательские ошибки в JavaScript
- Пространства имен в JavaScript
- Любопытное использование запятых в JavaScript
- Цепочка вызовов методов в JavaScript
- Как работать с отклонениями обещаний
- Как поменять местами два элемента массива в JavaScript
- Как я исправил ошибку "cb."применить не является функцией" ошибка при использовании Gitbook
- Как добавить элемент в начало массива в JavaScript
- Gatsby, исправьте ошибку «не удается найти модуль gatsby-cli / lib / reporter»
- Как получить индекс элемента в массиве JavaScript
- Как проверить пустой объект в JavaScript
- Как деструктурировать объект до существующих переменных в JavaScript
- Структура данных массива JavaScript
- Структура данных JavaScript стека
- Структуры данных JavaScript: очередь
- Структуры данных JavaScript: установить
- Структуры данных JavaScript: словари
- Структуры данных JavaScript: связанные списки
- JavaScript, как экспортировать функцию
- JavaScript, как экспортировать несколько функций
- JavaScript, как выйти из функции
- JavaScript, как найти символ в строке
- JavaScript, как фильтровать массив
- JavaScript, как расширить класс
- JavaScript, как найти дубликаты в массиве
- JavaScript, как заменить элемент массива
- JavaScript-алгоритмы: линейный поиск
- Алгоритмы JavaScript: двоичный поиск
- Алгоритмы JavaScript: сортировка выбора
- Алгоритмы JavaScript: Quicksort
- JavaScript-алгоритмы: сортировка слиянием
- JavaScript-алгоритмы: пузырьковая сортировка
- Дождитесь разрешения всех обещаний в JavaScript
Прототипное наследование - Блог Бамбелли
TIL как реализовать отношения наследования (с нуля) в javascript с использованием прототипного наследования
.
Во время занятий на прошлой неделе мы рассмотрели конструкторы и прототипы в контексте javascript. Студенты задавали отличные вопросы о расширяемости прототипа javascript и о том, как реализовать иерархии наследования, как в других объектно-ориентированных языках. Я обнаружил, что немного запутался в этой теме, поэтому я немного искал после класса и наткнулся на эту статью из MDN, которая хорошо объясняет.
Я решил попытаться объяснить, как реализовать отношения наследования в javascript! Читайте дальше, чтобы узнать больше.
Что такое прототипное наследование?
Javascript имеет другую систему для реализации иерархий наследования, чем те, которые используются в традиционных объектно-ориентированных языках, таких как C ++ и Java: прототипное наследование!
В отличие от C ++ и Java, объекты javascript не имеют унаследованной функциональности , скопированной поверх в наследующий объект от родительского объекта. Вместо этого функциональность связана с наследующим объектом через цепочку прототипов
, которая, по сути, определяет набор объектов, от которых наследуется конкретный объект. Связывание функциональности через цепочку прототипов
- это JavaScript-эквивалент систем наследования в традиционных конструкциях объектно-ориентированных классов.
Стоит отметить, что объект может иметь только один прототип, в результате чего javascript поддерживает только одиночное наследование (например, Java).
Прототипное наследование в действии: круговая иерархия (иерархия?)
Давайте посмотрим, как прототипное наследование выглядит в коде. Ниже приведен конструктор пирога: у пирогов есть размер и начинка, которую нужно добавить к каждому пирогу.
function Pie (размер, начинка) {
this.size = размер
this.toppings = начинки
}
Pie.prototype.getDescription = function () {
console.log (`Размер пирога $ {this.size} дюймов,
и имеет следующие начинки: $ {this.toppings.join (',')} `)
}
Обратите внимание, что я поместил метод getDescription
в прототип Pie. Назначение метода непосредственно прототипу гарантирует, что метод не переопределяется каждым экземпляром Pie и будет использоваться любым объектом, который существует в цепочке прототипов ниже Pie.
Пора Пицца (подкласс)!
Теперь я создам новый конструктор объекта для пиццы. Пицца - это
Pie, поэтому она должна наследовать от pie и создавать подклассы. В java мы бы написали public class Pizza extends Pie
для реализации этого типа отношений, но в javascript мы делаем следующее:
function Pizza (size, toppings, isDeepDish) {
Pie.call (это, размер, начинки)
this.isDeepDish = isDeepDish
}
Конструктор похож на Pie, добавляет новое поле с именем isDeepDish
, которое отражает, является ли пицца в стиле Чикаго (это всегда должно быть правдой, IMO…), но также использует метод call ()
для выполнения Pie конструктор. Pie.call (this, size, toppings)
- это JavaScript-эквивалент вызова super (properties)
внутри конструктора подкласса в Java.
Вызов
позволяет передать другой контекст выполнения функции, которая определена в другом месте вашей программы. В конструкторе Pizza, приведенном выше, мы передаем и
конструктора Pizza в качестве контекста выполнения в конструктор Pie, а также соответствующие аргументы, необходимые для этого конструктора. Это приводит к this.size
и this.toppings
определены для новых объектов Pizza, хотя эти свойства определены как часть конструктора Pie.
Завершение прототипа ссылки
В приведенном выше примере прототип Pizza еще не связан с цепочкой прототипов, содержащей Pie. В текущем состоянии прототип пиццы просто связан с самим конструктором пиццы. Это означает, что, поскольку метод getDescription
определен в прототипе Pie, этот метод недоступен для объектов Pizza и приведет к ошибке «метод не является функцией», если он вызывается для нового объекта Pizza.
Нам нужно связать прототип Pizza с Pie, чтобы мы унаследовали методы, определенные в прототипе Pie.
Боковое примечание: это поведение применяется только к методам, которые определены в родительском прототипе, а не к методам, которые определены внутри родительского конструктора.
Например, если getDescription
был определен как свойство на , этот
внутри конструктора Pie, тогда, когда мы выполнили Pie.call ()
внутри конструктора Pizza, метод был бы переопределен на новый объект "Пицца".
В следующем примере показано, как связать прототип Pie с Pizza.
Pizza.prototype = Object.create (Pie.prototype)
Pizza.prototype.constructor = Pizza
Первая строка устанавливает прототип пиццы в новый экземпляр объекта, хранящегося в Pie.prototype
, который выполняет связывание прототипа пиццы с цепочкой Pie! Это означает, что теперь у нас есть доступ к любым методам, которые определены в прототипе Pie в наших объектах Pizza.
Однако побочным эффектом перезаписи прототипа Pizza копией Pie’s является то, что мы также перезаписываем конструктор в Pizza.prototype.constructor
указателем на конструктор Pie. Вторая строка устраняет эту проблему, сбрасывая Pizza.prototype.constructor
на исходный конструктор Pizza.
Поскольку мы использовали Object.create ()
для установки нашего Pizza.prototype
, на прототип Pie не повлияют никакие дополнения, которые мы добавляем к Pizza.прототип
. Это потому, что Object.create ()
deepCopies и возвращает новый экземпляр переданного объекта, , таким образом изолируя Pizza.prototype
от Pie.prototype
.
Мы можем подтвердить привязку прототипов конструкторов, вызвав isPrototypeOf ()
для наших экземпляров объекта:
console.log (Pizza.prototype.isPrototypeOf (pizza)) // должно регистрировать истину
console.log (Pie.prototype.isPrototypeOf (pizza)) // должно регистрировать истину
консоль.log (Pizza.prototype.isPrototypeOf (pie)) // должно регистрировать ложь
Переопределение методов в цепочке
Как переопределение, так и частичное переопределение методов также возможно в javascript.
Чтобы полностью переопределить метод в цепочке прототипов, просто определите новый метод с тем же именем в дочернем прототипе.
Pizza.prototype.getDescription = function () {
console.log (`Эта пицца - глубокое блюдо $ {this.deepDish}`)
}
Вызов метода, описанного выше, для объекта пиццы не приведет к передаче полномочий дальше по цепочке прототипов, поскольку метод с именем getDescription
теперь определен на уровне Pizza.
Частичное переопределение родительских методов также возможно путем ссылки на метод родительского прототипа изнутри тела переопределения:
Pizza.prototype.getDescription = function () {
Pie.prototype.getDescription.call (this) // обратите внимание на `.call (this)`
console.log (`Эта пицца - глубокое блюдо $ {this.deepDish}`)
}
Вызов (этот)
важен, потому что без него Pie.prototype.getDescription
не будет иметь правильного контекста выполнения.Мы передаем контекст выполнения и этот
, который в нашем случае будет экземпляром объекта Pizza.
Заключительные мысли
Это было полезным упражнением по реализации прототипного отношения наследования с нуля в javascript. Таким образом, вот шаги, необходимые для создания подкласса, наследуемого от родительского конструктора:
1) Определите конструктор логического подкласса и внутри него вызовите требуемый конструктор суперкласса: Pie.call (this, size, toppings)
.
2) Установите прототип подкласса на глубокую копию прототипа суперкласса: Pizza.prototype = Object.create (Pie.prototype)
3) Сбросьте конструктор подкласса после перезаписи оригинала на шаге 2: Pizza.prototype.constructor = Pizza
Я многому научился из этого упражнения, и надеюсь, что вы тоже! Вы можете найти файл со всеми примерами кода, которые я просматриваю в этом сообщении в блоге, а также некоторые дополнительные комментарии в моем github.
Игра с объектной моделью Ruby: прототипное наследование
Jacek Galanciak
-26 января 2019 г.
–rubyГруппа четырех определила шаблон прототипа следующим образом:
Укажите типы объектов, которые нужно создать, используя прототипный экземпляр , и создайте новые объекты, скопировав этот прототип.
Другими словами, поведение наследуется от конкретных экземпляров, а не от классов. Самым популярным языком, использующим этот тип наследования, является JavaScript (до появления системы на основе классов в ES6).Хотя Ruby использует наследование на основе классов, его объектная модель достаточно гибкая, чтобы позволить нам реализовать базовое прототипное наследование.
Мы собираемся реализовать тираннозавра, начиная с обычного животного, через динозавра и заканчивая конкретным экземпляром тираннозавра.
Определение методов для объектов (не классов)
Поскольку Ruby позволяет расширять классы во время выполнения (концепция так называемых открытых классов ), неудивительно, что можно также расширять экземпляры.Это можно сделать тремя синонимами:
1
2str1 = "test string"
3def str1.big
4 upcase + "!"
5end
6
7
8str1.instance_eval do
9 def big
10 upcase + "!!"
11 конец
12 конец
13
14
15str2 = "test string"
16str2.define_singleton_method: big do
17 upcase + "!"
18end
19
20
21str3 = "test string"
22class << str3
23 def big
24 upcase + "!"
25 конец
26 конец
27
28стр1.big
29
30str2.big
31
32str3.big
33
Все три способа делают то же самое, но третья форма дает подсказку о том, как Ruby обрабатывает добавление метода к определенному объекту: он создает «анонимный» класс, вставляет в него метод и добавляет этот класс в цепочку наследования объекта. Этот класс называется собственным классом, или призрачным классом, или одноэлементным классом.
В этом легко убедиться: позвонив по телефону str1.singleton_methods
, str2.singleton_methods
, str3.singleton_methods
вернет [: big]
.
Если вы хотите узнать больше об одноэлементных классах, я настоятельно рекомендую прочитать Ruby's Anonymous Eigenclass: Putting the Ei in Team.
Назад к наследованию прототипа
Начнем с реализации общего прототипа животного.
1animal = Object.new
2def animal.taxonomic_name
3 "Animalia"
4end
5
6def animal.дышать
7 ставит «[вдыхает] [выдыхает]»
8end
9
10def animal .__ proto__
11 nil
12end
13
14def animal.taxonomic_rank
15 __proto__? (__proto __. taxonomic_rank + [taxonomic_name]). uniq: [taxonomic_name]
16end
Обратите внимание, как мы начали с создания пустого объекта и продолжили определение методов для этого конкретного экземпляра. Мы также можем увидеть намек на будущее: наши объекты будут хранить прототип внутри метода __proto__
и будут определять таксономический ранг животного, просматривая цепочку прототипов.
Нашему животному миру нужен конструктор. Неудивительно, что мы назовем его новый
:
1def animal.new
2 prototype_obj = self
3 new_obj = clone
4 new_obj.define_singleton_method: __ proto__ do
5 prototype_obj
5 prototype_obj 900 new_obj
8end
Вы можете задаться вопросом: почему мы использовали define_singleton_method
вместо любой другой формы? Ну, мы хотели, чтобы метод __proto__
возвращал значение self
из метода new
прототипа.Однако использование def
/ class
переключает лексическую область видимости, что означает, что любые значения, ранее определенные в блоке, не видны в теле метода, который мы определяем с помощью двух ключевых слов. К счастью, замыкания «запоминают» локальные значения из области, в которой они были определены, поэтому использование define_singleton_method
с блоком (замыкание) позволяет нам получить доступ к значению prototype_obj
внутри метода, который мы определяем.
Давайте протестируем нашу реализацию, создав динозавра из прототипа животного:
1dinosaur = animal.новый
2def dinosaur.taxonomic_name
3 "Dinosauria"
4end
5
6def dinosaur.walk
7 put "[walk]"
8end
9
10run dinosaur «[идет быстрее]»
12end
13
14dinosaur.breathe
15
16dinosaur.run
17
18dinosaur.taxonomic_rank
19
20animal.run так хорошо - у нас наследование работает правильно.Поскольку мы определили способность бегать на динозавре, обычное животное не может этого сделать, и попытка сделать это вызывает ожидаемое исключение.
Теперь давайте завершим цепочку, определив теропод, тиранозавр и конкретный экземпляр T-Rex:
1theropod = dinosaur.new
2def theropod.taxonomic_name
3 "Theropoda"
4end
. run
6 put "[работает довольно быстро]"
7end
8
9tyrannosaurus = theropod.новый
10def tyrannosaurus.taxonomic_name
11 «Тираннозавр»
12end
13
14t_rex = tyrannosaurus.new
15t_rex.run 900_13
16 900_13
17t_rex.run 900_13
16 900_13
17t_rex.run 900_13
16 900_13
17t
Работает нормально.
Наконец, давайте представим, что наша реализация динозавров основана на классах:
1 Животное = животное
2 Динозавр = динозавр
3 Теропод = теропод
4 Тираннозавр = тираннозавр
5
02 8
5
02 8
6
Тиранозавр.новый9t.taxonomic_rank
10
Не читая подробностей реализации, никто не подумает, что это не классовая система.
Что такое классы в Ruby?
Часто повторяется, что в Ruby все является объектом . Ни один Rubyist не должен удивляться тому, что
5.class
возвращаетInteger
и что мы можем представлять блоки кода как объектыProc
. Даже методы - это объекты (а операторы - это просто методы):15.0.method (: round) .class
2
35.0.method (: +). Class
4 => Method
А как насчет классов? Они тоже объекты!
1Class.class
2
3Class.class.class
4
5Class.ancestors
6
7A = Class.new do
8 def test_method
9 "Test"
9 "Test" 9 "Test"11end
12B = Class.new (A)
13B.ancestors
14
15B.new.test_method
16
Завершение
Полезно ли прототипное наследование в Ruby? Возможно нет. Некоторые люди скажут, что пропуск дорогостоящих конструкторов в пользу быстрого клонирования объекта или отказ от дорогостоящего поиска методов в цепочке наследования и использование вместо них локальных методов (клонированных из прототипа) может иметь преимущества в производительности, но это слабый аргумент в пользу любых высоких значений. -уровневый язык. Тем не менее, игра с объектной моделью путем реализации идиомы, отличной от Ruby, - отличная возможность систематизировать более сложные концепции этого языка.
Основы JavaScript: прототипное наследование | Тим Хан
Как разработчик JavaScript, знание того, как работает наследование в JavaScript, является важным знанием, которое очень пригодится во время собеседований (подсказка). Сегодня я хочу уделить время рассмотрению наследования в JavaScript и хороших / плохих аспектов наследования в JavaScript.
С учетом сказанного, давайте перейдем к делу!
Что такое наследование?
Наследование - это одна из четырех фундаментальных концепций объектно-ориентированного программирования ( Абстракция , Инкапсуляция , Наследование и Полиморфизм ).Наследование позволяет одному объекту принимать свойства другого существующего объекта.
Есть ли у JavaScript наследование?
Да! В частности, JavaScript использует тип наследования, называемый прототипным наследованием, который сильно отличается от классического наследования, используемого в таких языках, как Java.
Что такое прототипное наследование?
Чтобы понять прототипное наследование, нам сначала нужно понять цепочку прототипов .Цепочка прототипов представляет собой серию связей между каждым объектом и его прототипом , который служит «родительским объектом» до тех пор, пока цепочка не достигнет ссылки на null . Также прототип объекта можно задать вручную с помощью __proto__ . Возьмем пример:
const vehicle = {numWheels: 4} const car = {numDoors: 4} // Установка автомобиля в качестве прототипа объекта автомобиля
car .__ proto__ = vehicle
console.log (car.numWheels)
=> 4Обратите внимание, что объект car не содержит свойства с именем numWheels , но когда мы устанавливаем объект транспортного средства в качестве прототипа, car.numWheels равно 4. При прототипном наследовании, если свойство не существует в исходном объекте, JavaScript начнет поиск свойства из прототипа исходного объекта и продолжит движение вверх по цепочке прототипов, пока не найдет свойство или не достигнет null .
Хорошо или плохо?
У каждого человека это может быть разное. Я лично считаю, что это хорошо и плохо одновременно по следующим причинам:
Хорошо…
- Легко и просто
- Подходит для динамической природы JavaScript
- Меньше избыточности в кодовой базе
Плохо…
.
- Потенциально трудно понять людям, привыкшим к классической наследственности (например,Java)
- Прототипное наследование в JavaScript использует шаблон конструктора, в котором объекты наследуются от конструкторов другого объекта, а не от всего объекта, что, как я считаю, больше соответствует тому, что прототипное наследование пытается достичь