Привет, незнакомец!
Эта история об одной очень особенной части JavaScript, самом удобном искусственном языке в мире на данный момент (2019 г.).
(ссылки не выделенные)
(и Русская версия)
ИМО, без сомнения, Брендан Эйх, автор языка программирования JavaScript, - Выдающийся гений! И это не потому, что он говорит:
Эта история является своего рода философским взглядом на наследование в JavaScript, и я надеюсь, что это видение может основываться на самом важном источнике знаний: самой жизни, в ее созданиях и творениях. Я не знаю, было ли это источником видения для цепочки прототипов JavaScript, хотя, если так, это означает, что это так сильно, так сильно для меня, что ... трудно даже дышать ...
Начнем, и наша первая точка - это то, где мы включаем наше воображение и пытаемся избавиться от всех обстоятельств, предлогов и других не относящихся к делу побочных эффектов.
Мы возвращаемся назад в будущее в эпоху, предшествовавшую Интернету 199х годов.
Начиная с первых хакеров, которые изобрели все, что мы знаем, теперь мы можем увидеть эту картинку из прошлого: Netscape Navigator 2 в жесткой войне с Internet Explorer 3. Java только зародилась, и почти все, почти все, что есть сейчас в Интернете, еще не изобретено и может быть открыто заново. Возможно, ты был достаточно молод для тех старых добрых времен, как я, и ты все еще помнишь это потрясающее ощущение того, как все создается рядом с тобой.
Итак, с вашим очень мощным ПК на базе Intell Pentium 200 MMX ™ внутри, с 32 Мб памяти и Windows 3.11 или даже Windows 95, вы с нетерпением ждете. И у вас также установлены оба этих веб-браузера. Модем Dial-Up позволяет подключаться к глобальной сети для сбора новых данных, обучения, общения в чате и так далее. Однако остановитесь, пока нет разговоров по веб-страницам, потому что JavaScript все еще не создан. Возможно, вы используете какие-то системы отложенного обмена сообщениями, которые могут быть основаны на EMail или UseNet или даже что-то вроде техники связи в реальном времени с IRC.
Прошло пару лет, и все изменилось ... Теперь вы можете видеть анимацию снежинок на веб-страницах, празднование Рождества или Нового года. И вы удивляетесь, как это было сделано, и обнаруживаете, что внутри есть новая технология, называемая языком JavaScript. HTML для вас не новость, и вы начинаете изучать эту потрясающую и блестящую технологию. Также каким-то образом вы затем обнаруживаете CSS, и это тоже важно, потому что на самом деле все делается путем объединения трех из них вместе. Вот это да.
И вы также можете увидеть некоторые замечательные изменения в вашей Windows, теперь вы можете создать свое первое приложение, используя CScript или даже HTA (все еще работает).
Вы начинаете создавать свой первый веб-сервер, используя Perl или C ~ C ++, может быть даже какой-то bash скрипт, если вы начнете использовать Unix-подобную ОС. И все между ними связано с Common Gateway Interface (не что иное, как CGI). PHP пока почти не существует, и он вам тогда наверняка понравится.
200x Era. Теперь вы можете использовать JScript на сервере с ASP. Он очень похож на JavaScript, который вы используете для своих веб-страниц. Это так здорово. Вы думаете о своем собственном шаблоне, своего рода XML. А потом кто-то назвал AJAX всеми техниками динамической загрузки контента, которые вы использовали много лет назад. И они делают только XMLHTTPRequest для всего, хотя вы все еще можете думать о тегах BMP, iframe или даже ‹script›. А потом кто-то намекнул о JSON и о том, как его приятно использовать, но вы использовали его целую вечность из: document.write("<" + "script src=" + path + ">");
Это все не имеет значения сейчас, но вы все еще можете вспомнить как…
Тогда время от времени вы могли бы работать с Rhino и Nashorn, пытаясь угодить вашим Java-клиентам, использующим Alfresco или Asterisk. Вы слышали о грядущих реализациях JS на аппаратных микросхемах и задаетесь вопросом, что это будет. Также теперь есть jQuery и Backbone.
Затем вы смотрите на зимний снег наступающего 2010 года, и теперь вы знаете, что есть Game Changer номер один: N ode.js ®. Следующие десять лет вы будете играть с этой новой игрушкой, а здесь, в 2019 году, до сих пор не можете поверить, насколько она хороша.
В общем, вам все это нравится, а игрушки и игры с ними - большая часть ваших жизненных интересов.
Но есть один небольшой вопрос, который вы задаете себе изо дня в день, из ночи в ночь на протяжении более двух десятилетий:
Как вы объясните сочувствие в JavaScript?
Вы знаете, что одна из самых сложных тем в JavaScript - это Система наследования и цепочка прототипов. Вам это нравится, вы можете объяснить, как это работает, потому что вы узнали это с очень ранних моментов, еще до того, как Первый Стандарт действительно был сделан, и где, как вы помните, находится 4.2.1 Объекты:
ECMAScript поддерживает наследование на основе прототипов. С каждым конструктором связан прототип, и каждый объект, созданный с помощью этого конструктора, имеет неявную ссылку к прототипу (так называемому прототипу объекта), связанному с его конструктором. Кроме того, прототип может иметь ненулевую неявную ссылку на свой прототип, и так далее; это называется цепочкой прототипов.
Вау ... И если вы, как и я, действительно думаете, что это одно из самых важных изобретений CS, то как бы вы объяснили тот эффект, который он произвел на вас, и все возможности, которые возникли в вашем воображении, когда вы его читали?
Вернемся к началу. Настал 1995 год. Вы Брендан Эйх, и вам нужно изобретать новый язык программирования. Наверное, вам нравится Лисп или Схема, хотя бы в некоторых частях. И еще есть проблема наследования, которую вы должны как-то решить: потому что в этом новом языке должно быть какое-то ООП. Итак, подумайте: вам нужно смешать все, что вам нравится, и, возможно, некоторые вещи, которые вам не нравятся, и сделать этот коктейль Достаточно хорошим, чтобы никто не увидел разницы, пока не появится реальный повод заглянуть внутрь.
И теперь снова возникает вопрос: как вы продвинетесь с наследованием?
А теперь давайте вернемся к нашей обычной жизни. Что все мы знаем о наследовании? Некоторые очевидные части ответов на этот вопрос:
- Больше всего жизнь основана на Геноме. Это хранилище данных о возможных свойствах и поведении существ. Каждый из нас может сделать вывод и владеть своей частью, будучи живым из предыдущего поколения жизненного цикла.
- Вы можете создать существо двумя способами: комбинируя двух предшественников или моноклонное клонирование одного из них. Конечно, сегодня можно смешать некоторые части генома из более чем двух, но это не так естественно и очевидно.
- Время имеет значение. Если некоторые необходимые свойства еще не изобретены или больше не существуют, вы не можете их унаследовать, вы можете воссоздать их только с нуля, будучи дизайнером генома. Кроме того, есть наследие чего-то, чем вы владеете, от ваших предшественников не через геном, а по закону собственности, и это тоже может быть важно.
Итак, мы снова здесь, и правильный вопрос для нашего недавно созданного языка: Наследование того, что мы должны спроектировать?
И в любом случае мы должны иметь возможность заполнить пробелы между программированием и жизнью, решая эту проблему наследования, просто потому, что в этом случае мы вообще не сможем назвать это наследованием.
И еще одно: мы находимся в 1995 году, используем очень мощный компьютер с объемом памяти всего 32 Мб, и мы пытаемся реализовать язык сценариев, поэтому мы должны позаботиться об этой памяти, мы должны иметь возможность использовать ее в небольшом количестве. насколько это возможно. Каждая часть данных, особенно Строки, потребляет много памяти, и мы должны иметь возможность определить эту часть только один раз, а затем делать ссылки столько раз, сколько нам нужно для доступа к нашим данным с помощью некоторых методов. Теперь мы видим, насколько сложным был этот вопрос.
Бытует мнение, что JavaScript сделан из« Объектов », поэтому мы можем ответить на вопрос, что должно наследовать от и в: Объекты. И, думая об этой экономии памяти, мы должны хранить наши данные в объектах, также смешивая их ссылки на данные между унаследованными свойствами этих объектов. Теперь мы можем понять, почему в 1995 году нам действительно нужен дизайн, основанный на цепочке прототипов: это поможет нам добиться экономии памяти, поскольку насколько это возможно. И я думаю, что это все еще имеет значение.
И здесь снова можно положиться на дизайн. Основываясь на мнении все является объектом, мы могли бы клонировать что-нибудь. И что такое клонирование? Как я думаю, описывая наши требования, мы могли бы иметь в виду что-то вроде Structure Clones, или Shallow Copies, или некоторых современных предков Object.assign. Еще в 1995 году мы могли использовать просто копию структуры, поэтому мы можем использовать для этого часть кода, которая работает с концепцией for (var i in ClonedObject){}
, потому что это уже было изобретено для первой версии стандарта:
Также я бы рекомендовал заглянуть вглубь для понимания создания клонов с помощью JavaScript вместо очевидной for ... in
реализации. Затем давайте попробуем представить, как последовательное использование этого определения клонирования поможет нам прийти к следующим объяснениям схемы клонирования, которая, похоже, работает в прошлые, старые, древние времена:
- Объект из конструктора клонирование: мы будем использовать конструктор для создания как минимум двух новых разных клонов:
- Конструктор из Конструктора клонирование: мы будем использовать один Конструктор для реализации поведения другого Конструктора:
- Конструктор из объекта клонирование: мы будем использовать объект для создания как минимум двух новых конструкторов с клонированными из этого объекта реквизитами:
- Объект из объекта клонирование: мы будем использовать объект для создания как минимум нескольких новых различных клонированных объектов. Так что это всего лишь
cloneProp(cloned, destination)
пример, описанный выше.
Как мы видим, клонирование очевидно, это нормально, работает нормально, но…
Насколько мы сделаем наследование для экземпляров, используя метод комбинаций предшественников?
- Наследовать объект из конструктора: поскольку он сам является целью конструктора, его также можно использовать:
- Наследовать конструктор от конструктора. Несомненно, первым, кто это сделал, был гений. Это тоже классический всем известный пример.
- Inherit Constructor from Object: вы просто полагаетесь на
.prototype = object
каждый раз, когда делаете наследование, поэтому здесь нечего описывать дополнительно, он всегда включен, и в любое время вы можете изменитьConstructor.prototype
, и он немедленно перебросит все унаследованные экземпляры, потому что память распределяется между ними. - Наследовать объект от объекта: вот что это такое, снова! Вы просто помещаете любой Существующий объект в
Constructor.prototype
, и после вызова конструкцииnew Constructor
вы получите новый Унаследованный экземпляр этого Существующий предшествующий объект. И вы явно поместили существующий объект вConstructor.prototype
, только тогда будет неявная ссылка. И только тогда instanceof, который изначально был описан почти через четыре года после появления цепочки прототипов в JS, будет полагаться на этот конструктор.
Но все же одно от Standart: делайте все это настолько глубоко, насколько это будет необходимо.
`и так далее`
для цепочки прототипов нашего наследования Trie 1995 года.
Давайте попробуем сделать экземпляр Наследование действительно глубоким в 1995 году
Действительно, предположим, что у нас есть два экземпляра { objects }
, не конструкторы, а просто объекты. И мы хотим унаследовать одно от другого, и, возможно, от другого, и еще, как в Стандарте сказано: «и так далее«?
Но как?
Посмотрим дальше, глубже. Правильный вопрос здесь, опять же, который мы уже заметили ранее: Наследование того, что мы должны проектировать?
Нам не нужны сами экземпляры. Нам нужны их свойства: связанные данные, потребляющие память; а также нам потребуется некоторое поведение: методы, использующие эти данные. Было бы справедливо, если бы еще была возможность проверять, откуда и куда мы собираемся Наследовать. Было бы также хорошо, если бы мы могли воспроизвести этот дизайн шаблона наследования в будущем, то есть, если и когда мы унаследуем один от другого, мы всегда получим тот же результат, в зависимости от того, что мы ожидаем по описанию (контракту). Хотя может быть также полезно как-то зафиксировать этот момент создания, потому что, действительно, предшественники могут измениться со временем, и было бы не так хорошо, что мы изменим преемника с учетом этих изменений.
Поскольку весь наш код представляет собой комбинацию данных и поведения, было бы хорошо, если бы мы смешали их вместе, используя этот шаблон наследования?
На мой взгляд, все это похоже на то, что мы видим, когда наблюдаем Жизнь во всех ее величественных формах. От самых ранних одноклеточных существ к их многоклеточным преемникам, а затем к другим преемникам, затем к животным ... и затем к людям и человечеству, племенам, цивилизациям и к разуму, к Космос и ИИ и полет в Галактику, к звездам…
и: «… Все, что нам нужно сделать, это убедиться, что мы продолжаем говорить…»
эта невероятно внимательная цитата из Стивена Хокинга, которая затем была популяризирована потрясающим шедевром Pink Floyd.
И Языки программирования, использующие концепции Передача сообщений и На основе потока, являются преемниками этой идеи. На основе только сырых данных мы разрабатываем все на основе этого API базового уровня. Я думаю, что это уже искусство, и мы можем увидеть, как это работает и для JavaScript, глубоко вложенного в структуру языка: ссылки на структуры данных через цепочку прототипов.
Итак, предположим, что у нас есть оба Родителя, они общаются (продолжайте говорить) через время, а затем в один момент они решают объединить свои эмоции и чувства вместе, создав Ребенка. Затем этот ребенок вырос, встретив другого взрослого ребенка, они общаются (продолжайте говорить) и рожают еще одного ребенка. И снова, и снова, и снова, с древних времен, чтобы точно определить этот момент: это Круг Жизни.
И, как мы видим, естественно мы должны использовать двух родителей, ни меньше, ни больше, потому что если мы используйте один, это будет клонирование, если мы используем больше двух, это будет инженерия: Разработанный геном ДНК.
Два существа, а не Меньше или Больше. И они объединяют свои свойства, используя Наследование, и затем их дети становятся владельцами их Наследия. И каждый момент, когда они объединяются, они дают нам нового ребенка. Это так просто.
Звучит странно, хотя, да, у нас есть все части, необходимые для создания шаблона наследования в JavaScript, начиная с 1995 года. И главная часть заключается в том, что 4.2.1 Objects с его неявным ссылки через прототип.
И в этом случае вы объединяете родительский объект с ParentConstructor через его . прототип и затем этот Конструктор, вероятно, сделает вас ChildObject, когда вы произносите новое ключевое слово:
Здесь у нас есть оба родителя, и в тот момент, когда мы говорим новый, мы просим их пообщаться. Если они не хотят общаться, процесс завершится ошибкой, и Life (js runtime compiler) скажет вам, что пошло не так.
И затем, конечно, мы спрашиваем о генетическом дереве или, что может быть намного проще, о какой-то генеалогии. И ответ тот же: ваш Дочерний объект вырос, а стал родительским объектом, а затем встретил другой Объект-конструктор и когда вы говорите новый - отлично:
И вы можете продолжать делать это настолько глубоко, насколько это будет необходимо!
Я действительно надеюсь, что это была основная идея при разработке дизайна Prototype Chain, потому что, как все мы знаем, этот способ вызывает некоторые аккуратные проблемы ...
1: Сообщество… Как вы можете легко проверить, присвоение .prototype элемента ParentConstructor или AnotherConstructor - очень сильный социальный контракт для наших племен, он создает ссылки из свойств ParentObject. (.foo
) Наследникам: kids, ChildObject и SequentialChildObject. Если вы измените это назначение, ой, эти ссылки исчезнут навсегда. Если вы обманываете и переназначаете эти ссылки, упс, наши дети наследуют реквизиты другого объекта. Итак, при объединении родителей с присвоением .prototype, вероятно, мы можем сказать, что собираемся создать Семья , потому что тогда наши родители могут произвести много детей, и, используя ключевое слово новое , мы можем попросить их родить еще одного ребенка столько времени, сколько потребуется для нашей Истории жизни . И затем, если мы уничтожим эту ссылку на основе .prototype, мы уничтожим все свойства детей, унаследованные ими от семьи, такая криминальная драма. ; ^)
Следовательно, все это касается Legacy, и мы должны позаботиться об этом, если мы собираемся создавать надежный и обслуживаемый код. Конечно, соблюдение SOLID, Принцип замещения Лисков на Дизайн по контракту, а затем, вероятно, некоторые из GRASP не были той проблемой в 1995 году. Но очевидно, что все методологии создавались не с нуля, они много родились. ранее.
2: Семья… Как мы можем легко проверить, наш ParentObject может быть очень несерьезным в сочетании с другими конструкторами. Это несправедливо, но мы можем использовать столько конструкторов, сколько захотим, наследование ParentObject для создания других дочерних семейств. С другой стороны, каждый конструктор тесно связан с ParentObject путем присвоения .prototype . Если мы не желаем вреда детям, мы должны сохранять эту ссылку как можно дольше и дольше. Все это можно назвать трагедией и искусством нашего Племени История. Хотя также это защитит нас от Амнезии того, на что мы ссылались из и на, и почему наши у детей есть все это Наследие. Отдавая должное Мнемозине, действительно легко протестировать нашу цепочку прототипов Trie, найдя Артефакты, что мы сделали не так.
3: Старение… наш ParentObject и наш Конструктор могли быть каким-то образом повреждены во время нашей Жизни (время выполнения) делает то, для чего он был разработан. Мы можем заботиться о том, что делаем, но теперь никто не защищен от ошибок. И все эти изменения могут нанести некоторый вред нашим наследникам через эту цепочку прототипов. Не стоит забывать об утечках памяти. Возможно, мы сможем уничтожить ненужные части нашего кода. Возможно, мы сможем освободить память, которая больше не используется для нашего жизненного цикла. Также мы должны избавиться от возможности привносить Temporal Paradox в наши цепочки прототипов, хотя ссылаться на Parent from Child легко, это может быть очень вредно, поэтому мы не должны использовать эту технику перехода из будущего в прошлое. Наконец, можно получить полный стек или кучу трудно воспроизводимых хайзенбагов, если мы попытаемся измерить что-то, что можно изменить с течением времени.
Хроника решения
Это легко, очевидно и очень приятно. Вместо того, чтобы думать о нашем конструкторе как о маме, а о нашем ParentObject как о папе, давайте попробуем описать их в терминах яйцеклетка и… ох… пыльца. Итак, когда мы создадим Зиготу с использованием ключевого слова новое, тогда, что ж, нашему Воображению не будет никакого вреда.
Таким образом мы избавимся от всех трех проблем. Наверняка нам понадобится Фабрика Яичных Клеток: Фабрика Конструкторов. Это может быть каждый из наших родителей, может быть, мать или отец, как хотите, но главное здесь, когда мы хотим сказать новый, мы должны создать Яйцеклетку и принести ей пыльцу для выращивания новых. Цветок Галантуса такой далекой и еще снежной и ветреной Весной 2020 года:
Безусловно, Цикломатическая сложность решений, которые вы будете делать с использованием этого шаблона, абсолютно выдающаяся, и, как мне кажется, их можно сравнить с тем, что вы видите в решении Zebra Puzzle. Поэтому я сделал библиотеку, которая могла бы помочь с конструкторами цепочки и мемоизации.
Я не могу это доказать, но этот метод успешно используется в течение двух десятилетий, время от времени, когда вам нужно быть уверенным, что вы сделали все возможное с наследованием. Как вы можете проверить, он более чем поддается тестированию, воспроизводству и обслуживанию. Мы не рассказываем здесь всю историю, мы просто полагаемся на факт: JavaScript разработан достаточно хорош даже для создания генеалогического трие с наследованием. Также мы не обсуждали деградацию Class, но вы можете легко реализовать FlowerEggCellClass вместо просто FlowerEggCell внутри FlowersFactory. Основная часть здесь такова: если вы захотите использовать instanceof для проверки своих цветов, вы увидите, что все они сделаны из этой FlowerEggCell конструкторы, на которые вы ссылались через FlowerZygote. И, конечно, вы можете вовремя изменить FlowerZygote, это не принесет вреда FlowersFactory, продолжать иметь возможность производить новые ссылочные конструкторы в будущем в соответствии с разработанным вами дизайном.
Я надеюсь, что эта статья раскроет всю неопределенность в отношении важности ключевого слова .prototype, а затем увидит использование null
вместо this
для .bind(null
, .call(null
или .apply(null
вы почувствуете скорбь о текущем состоянии дизайна созданного ими стиля кода.
Спасибо за чтение этого!
Всему свое время!
С уважением V