Я подошел к моменту, когда мне нужно иметь какое-то рудиментарное множественное наследование в JavaScript. (Я здесь не для того, чтобы обсуждать, хорошая это идея или нет, поэтому, пожалуйста, оставьте эти комментарии при себе.)
Я просто хочу знать, пытался ли кто-нибудь это с успехом (или нет) и как они это сделали.
Вкратце, мне действительно нужно иметь объект, способный наследовать свойство от более чем одной цепочки прототипов (т.е. каждый прототип может иметь свою собственную цепочку), но в заданном порядке приоритета (он будет поиск по цепочкам для первого определения).
Чтобы продемонстрировать, как это теоретически возможно, это может быть достигнуто путем присоединения вторичной цепи к концу первичной цепи, но это повлияет на все экземпляры любого из этих предыдущих прототипов, а это не то, что я хочу.
Мысли?
Ответы:
Множественное наследование может быть достигнуто в ECMAScript 6 с помощью прокси-объектов .
Реализация
объяснение
Прокси-объект состоит из целевого объекта и некоторых ловушек, которые определяют настраиваемое поведение для основных операций.
При создании объекта, который наследуется от другого, мы используем
Object.create(obj)
. Но в этом случае нам нужно множественное наследование, поэтому вместоobj
я использую прокси, который перенаправит основные операции на соответствующий объект.Я использую эти ловушки:
has
Ловушка ловушка дляin
оператора . Я использую,some
чтобы проверить, содержит ли хотя бы один прототип свойство.get
Ловушка ловушка для получения значений свойств. Я использую,find
чтобы найти первый прототип, содержащий это свойство, и возвращаю значение или вызываю метод получения на соответствующем приемнике. Этим занимаетсяReflect.get
. Если ни один из прототипов не содержит свойства, я возвращаюсьundefined
.set
Ловушка представляет собой ловушку для установки значений свойств. я используюfind
чтобы найти первый прототип, который содержит это свойство, и вызываю его установщик на соответствующем приемнике. Если сеттер отсутствует или прототип не содержит свойства, значение определяется в соответствующем приемнике. Этим занимаетсяReflect.set
.enumerate
Ловушка представляет собой ловушку дляfor...in
петель . Я повторяю перечислимые свойства из первого прототипа, затем из второго и так далее. После итерации свойства я сохраняю его в хеш-таблице, чтобы не повторять его снова.Предупреждение : эта ловушка была удалена в черновике ES7 и устарела в браузерах.
ownKeys
Ловушка ловушка дляObject.getOwnPropertyNames()
. Начиная с ES7,for...in
циклы продолжают вызывать [[GetPrototypeOf]] и получать собственные свойства каждого из них. Поэтому, чтобы заставить его перебирать свойства всех прототипов, я использую эту ловушку, чтобы все перечисляемые унаследованные свойства выглядели как собственные.getOwnPropertyDescriptor
Ловушка ловушка дляObject.getOwnPropertyDescriptor()
.ownKeys
Недостаточно сделать все перечисляемые свойства как собственные свойства в ловушке,for...in
циклы получат дескриптор для проверки, являются ли они перечислимыми. Поэтому я используюfind
для поиска первого прототипа, который содержит это свойство, и повторяю его цепочку прототипов, пока не найду владельца свойства, и не верну его дескриптор. Если ни один из прототипов не содержит свойства, я возвращаюсьundefined
. Дескриптор изменен, чтобы сделать его настраиваемым, иначе мы могли бы нарушить некоторые инварианты прокси.preventExtensions
ИdefineProperty
ловушки включены только , чтобы предотвратить эти операции от изменения прокси - цели. В противном случае мы можем нарушить некоторые инварианты прокси.Доступны другие ловушки, которые я не использую
getPrototypeOf
Ловушки могут быть добавлены, но не правильный способ вернуть несколько прототипов. Из этого следуетinstanceof
тоже не сработает. Поэтому я позволил ему получить прототип цели, который изначально равен нулю.setPrototypeOf
Ловушка может быть добавлена и принять массив объектов, который заменит прототипы. Это оставлено читателю в качестве упражнения. Здесь я просто позволил ему изменить прототип цели, что не очень полезно, потому что ни одна ловушка не использует цель.deleteProperty
Ловушка представляет собой ловушку для удаления собственных свойств. Прокси-сервер представляет собой наследование, поэтому в этом нет особого смысла. Я позволил ему попытаться удалить цель, которая в любом случае не должна иметь свойства.isExtensible
Ловушка ловушка для получения растяжимости. Не очень полезно, учитывая, что инвариант заставляет его возвращать ту же расширяемость, что и цель. Поэтому я просто позволил ему перенаправить операцию на цель, которая будет расширяемой.apply
Иconstruct
ловушка ловушка для вызова или инстанцирования. Они полезны только тогда, когда целью является функция или конструктор.пример
источник
multiInherit(o1={a:1}, o2={b:2}, o3={a:3, b:3})
Object.assign
) или получают совершенно другой график, в конце концов все они получают единственную цепочку прототипов между объектами. Прокси-решение предлагает ветвление во время выполнения, и это здорово!Обновление (2019 г.): исходный пост уже устарел. Эта статья (теперь ссылка на интернет-архив, поскольку домен исчез) и связанная с ней библиотека GitHub - хороший современный подход.
Исходное сообщение: Множественное наследование [править, не правильное наследование типа, но свойств; mixins] в Javascript довольно просто, если вы используете сконструированные прототипы, а не общие-объектные. Вот два родительских класса для наследования:
Обратите внимание, что я использовал один и тот же элемент «name» в каждом случае, что могло быть проблемой, если бы родители не пришли к соглашению о том, как следует обращаться с «name». Но в этом случае они совместимы (на самом деле избыточны).
Теперь нам просто нужен класс, наследующий от обоих. Наследование осуществляется вызова функции конструктора (без использования ключевого слова new) для прототипов и конструкторов объектов. Во-первых, прототип должен унаследовать от родительских прототипов.
И конструктор должен унаследовать от родительских конструкторов:
Теперь вы можете выращивать, есть и собирать разные экземпляры:
источник
Array.call(...)
но, похоже, это не влияет на то, за что я схожуthis
.Array.prototype.constructor.call()
Он используется
Object.create
для создания настоящей цепочки прототипов:Например:
вернется:
так что
obj.a === 1
,obj.b === 3
и т. д.источник
Мне нравится реализация структуры классов Джона Ресига: http://ejohn.org/blog/simple-javascript-inheritance/
Это можно просто расширить до чего-то вроде:
что позволит вам передать несколько объектов для наследования. Здесь вы потеряете
instanceOf
возможности, но это само собой разумеющееся, если вы хотите множественное наследование.мой довольно запутанный пример вышеизложенного доступен по адресу https://github.com/cwolves/Fetch/blob/master/support/plugins/klass/klass.js
Обратите внимание, что в этом файле есть мертвый код, но он позволяет множественное наследование, если вы хотите взглянуть.
Если вам нужно цепное наследование (НЕ множественное наследование, но для большинства людей это одно и то же), это можно выполнить с помощью класса, например:
что сохранит исходную цепочку прототипов, но у вас также будет много бессмысленного кода.
источник
Не запутайтесь с реализациями множественного наследования в фреймворке JavaScript.
Все, что вам нужно сделать, это использовать Object.create () для создания нового объекта каждый раз с указанным объектом-прототипом и свойствами, а затем обязательно изменять Object.prototype.constructor на каждом этапе пути, если вы планируете создать экземпляр
B
в будущее.Для того, чтобы наследовать свойства экземпляра
thisA
иthisB
мы используем Function.prototype.call () в конце каждой функции объекта. Это необязательно, если вы заботитесь только о наследовании прототипа.Запустите где-нибудь следующий код и обратите внимание
objC
:B
наследует прототип отA
C
наследует прототип отB
objC
является примеромC
Это хорошее объяснение вышеперечисленных шагов:
ООП в JavaScript: что нужно знать
источник
Я никоим образом не эксперт по ООП javascript, но, если я правильно вас понял, вам нужно что-то вроде (псевдокода):
В этом случае я бы попробовал что-то вроде:
источник
c.prototype
нескольких раз не приводит к созданию нескольких прототипов. Например, если бы вы это сделалиAnimal.isAlive = true
,Cat.isAlive
все равно не было бы определено.В JavaScript можно реализовать множественное наследование, хотя очень немногие библиотеки это делают.
Я мог бы указать Ring.js , единственный известный мне пример.
источник
Сегодня я много работал над этим и сам пытался добиться этого в ES6. Я сделал это с помощью Browserify, Babel, а затем я протестировал его с Wallaby, и, похоже, он работал. Моя цель - расширить текущий массив, включить ES6, ES7 и добавить некоторые дополнительные настраиваемые функции, которые мне нужны в прототипе для работы с аудиоданными.
Валлаби проходит 4 моих теста. Файл example.js можно вставить в консоль, и вы увидите, что свойство includes находится в прототипе класса. Я все еще хочу проверить это завтра.
Вот мой метод: (Я, скорее всего, проведу рефакторинг и перепакую как модуль после некоторого сна!)
Репозиторий Github: https://github.com/danieldram/array-includes-polyfill
источник
Я считаю, что это до смешного просто. Проблема здесь в том, что дочерний класс будет ссылаться только
instanceof
на первый класс, который вы вызываете.https://jsfiddle.net/1033xzyt/19/
источник
Проверьте код ниже, который показывает поддержку множественного наследования. Сделано с использованием ПРОТОТИПНОГО НАСЛЕДОВАНИЯ
источник
У меня есть функция, позволяющая определять классы с множественным наследованием. Это позволяет использовать следующий код. В целом вы заметите полный отход от собственных методов классификации в javascript (например, вы никогда не увидите
class
ключевое слово):чтобы произвести такой вывод:
Вот как выглядят определения классов:
Мы видим, что каждое определение класса, использующее
makeClass
функцию, принимаетObject
имена родительских классов, сопоставленные с родительскими классами. Он также принимает функцию, которая возвращаетObject
содержащие свойства для определяемого класса. Эта функция имеет параметрprotos
, который содержит достаточно информации для доступа к любому свойству, определенному любым из родительских классов.Последняя необходимая часть - это
makeClass
сама функция, которая выполняет довольно много работы. Вот он вместе с остальным кодом. ЯmakeClass
довольно много комментировал :makeClass
Функция также поддерживает свойство класса; они определяются путем добавления к именам свойств$
символа (обратите внимание, что окончательное имя свойства будет$
удалено). Имея это в виду, мы могли бы написать специализированныйDragon
класс, который моделирует «тип» Дракона, где список доступных типов Дракона хранится в самом Классе, а не в экземплярах:Проблемы множественного наследования
Любой, кто
makeClass
внимательно следил за кодом для, заметит довольно существенное нежелательное явление, незаметно происходящее при выполнении приведенного выше кода: создание экземпляра aRunningFlying
приведет к ДВА вызовамNamed
конструктора!Это потому, что граф наследования выглядит так:
Когда существует несколько путей к одному и тому же родительскому классу в графе наследования подкласса , экземпляры подкласса будут вызывать конструктор этого родительского класса несколько раз.
Бороться с этим нетривиально. Давайте посмотрим на несколько примеров с упрощенными именами классов. Мы рассмотрим класс
A
, наиболее абстрактный родительский класс, классыB
иC
, которые наследуются от иA
, и класс,BC
который наследуется отB
иC
(и, следовательно, концептуально "наследует двойное" отA
):Если мы хотим предотвратить
BC
двойнойA.prototype.init
вызов, нам может потребоваться отказаться от стиля прямого вызова унаследованных конструкторов. Нам понадобится некоторый уровень косвенного обращения, чтобы проверить, происходят ли повторяющиеся вызовы, и короткое замыкание, прежде чем они произойдут.Мы могли бы рассмотреть вопрос об изменении параметров , подаваемых на свойства функционируют: наряду
protos
,Object
содержащие исходные данные , описывающие наследственные свойства, мы могли бы также включать в себя функцию полезности для вызова метода экземпляра таким образом , что методы родительских также называют, но повторяющиеся вызовы обнаружены и предотвратил. Давайте посмотрим, где мы устанавливаем параметры дляpropertiesFn
Function
:Вся цель вышеупомянутого изменения в
makeClass
том, чтобы у нас был дополнительный аргумент, предоставляемый нашемуpropertiesFn
при вызовеmakeClass
. Мы также должны знать, что каждая функция, определенная в любом классе, теперь может получать параметр после всех своих других с именем nameddup
, которыйSet
содержит все функции, которые уже были вызваны в результате вызова унаследованного метода:Этот новый стиль фактически обеспечивает
"Construct A"
регистрацию только один раз приBC
инициализации экземпляра . Но есть три минуса, третий из которых очень критичный :util.invokeNoDuplicates
функцией скрывается много сложностей , и размышления о том, как этот стиль позволяет избежать множественных вызовов, не интуитивно понятны и вызывают головную боль. У нас также есть этот надоедливыйdups
параметр, который действительно нужно определять для каждой отдельной функции в классе . Уч.NiftyClass
переопределяет функциюniftyFunction
и использует ееutil.invokeNoDuplicates(this, 'niftyFunction', ...)
для запуска без повторного вызова,NiftyClass.prototype.niftyFunction
вызовет функцию с именемniftyFunction
каждого родительского класса, который ее определяет, проигнорирует любые возвращаемые значения из этих классов и, наконец, выполнит специализированную логикуNiftyClass.prototype.niftyFunction
. Это единственно возможная структура . ЕслиNiftyClass
наследуетCoolClass
иGoodClass
, и оба этих родительских класса предоставляютniftyFunction
собственные определения,NiftyClass.prototype.niftyFunction
никогда (без риска многократного вызова) не смогут:NiftyClass
сначала специализированную логику , затем специализированную логику родительских классовNiftyClass
в любой момент, кроме как после завершения всей специализированной родительской логики.niftyFunction
целомКонечно, мы могли бы решить каждую указанную выше проблему, указав специализированные функции в
util
:util.invokeNoDuplicatesSubClassLogicFirst(instance, fnName, ...)
util.invokeNoDuplicatesSubClassAfterParent(parentName, instance, fnName, ...)
(гдеparentName
имя родителя, чья специализированная логика будет сразу следовать специализированной логике дочерних классов)util.invokeNoDuplicatesCanShortCircuitOnParent(parentName, testFn, instance, fnName, ...)
(в этом случаеtestFn
будет получен результат специализированной логики для указанного родительского объектаparentName
и будет возвращеноtrue/false
значение, указывающее, должно ли произойти короткое замыкание)util.invokeNoDuplicatesBlackListedParents(blackList, instance, fnName, ...)
(в этом случаеblackList
будетArray
родительским именем, специализированная логика которого должна быть полностью опущена)Все эти решения доступны, но это полный хаос ! Для каждой уникальной структуры, которую может принимать вызов унаследованной функции, нам потребуется специальный метод, определенный в разделе
util
. Какая абсолютная катастрофа.Имея это в виду, мы можем начать видеть проблемы реализации хорошего множественного наследования. Полная реализация того, что
makeClass
я предоставил в этом ответе, даже не рассматривает проблему множественного вызова или многие другие проблемы, которые возникают в отношении множественного наследования.Этот ответ становится очень длинным. Я надеюсь, что
makeClass
включенная мной реализация по-прежнему полезна, даже если она не идеальна. Я также надеюсь, что любой, кто интересуется этой темой, получил больше контекста, о котором нужно помнить при дальнейшем чтении!источник
Взгляните на пакет IeUnit .
Освоение концепций, реализованное в IeUnit, кажется, предлагает то, что вы ищете, довольно динамично.
источник
Вот пример цепочки прототипов с использованием функций-конструкторов :
В этой концепции используется определение «класса» для JavaScript, данное Иегудой Кацем :
В отличие от подхода Object.create , когда классы построены таким образом, и мы хотим создать экземпляры «класса», нам не нужно знать, от чего наследуется каждый «класс». Мы просто используем
new
.Порядок приоритета должен иметь смысл. Сначала он просматривает объект-экземпляр, затем его прототип, затем следующий прототип и т. Д.
Мы также можем изменить прототипы, что повлияет на все объекты, построенные в классе.
Изначально я написал кое-что из этого с этим ответом .
источник
child
наследуется отparent1
иparent2
). Ваш пример говорит только об одной цепочке.Опоздавшим на сцену является SimpleDeclare . Однако, имея дело с множественным наследованием, вы все равно получите копии исходных конструкторов. Это необходимость в Javascript ...
Merc.
источник
Я бы использовал ds.oop . Он похож на prototype.js и другие. делает множественное наследование очень простым и минималистичным. (всего 2 или 3 КБ) Также поддерживает некоторые другие полезные функции, такие как интерфейсы и внедрение зависимостей.
источник
Как насчет этого, он реализует множественное наследование в JavaScript:
А вот код служебной функции specialize_with ():
Это настоящий исполняемый код. Вы можете скопировать и вставить его в файл HTML и попробовать сами. Это работает.
Это попытка реализовать MI в JavaScript. Не много кода, больше ноу-хау.
Пожалуйста, посмотрите мою полную статью об этом, https://github.com/latitov/OOP_MI_Ct_oPlus_in_JS
источник
Я просто назначал классы, которые мне нужны, в свойствах других, и добавлял прокси для автоматического перехода к ним, которые мне нравятся:
источник