Множественное наследование/прототипы в JavaScript

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

Я просто хочу знать, пытался ли кто-нибудь сделать это с каким-либо (или нет) успехом, и как они это сделали.

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

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

Мысли?

Ответ 1

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

Ответ 2

Множественное наследование может быть достигнуто в ECMAScript 6 с помощью Объектов прокси.

Реализация

function getDesc (obj, prop) {
  var desc = Object.getOwnPropertyDescriptor(obj, prop);
  return desc || (obj=Object.getPrototypeOf(obj) ? getDesc(obj, prop) : void 0);
}
function multiInherit (...protos) {
  return Object.create(new Proxy(Object.create(null), {
    has: (target, prop) => protos.some(obj => prop in obj),
    get (target, prop, receiver) {
      var obj = protos.find(obj => prop in obj);
      return obj ? Reflect.get(obj, prop, receiver) : void 0;
    },
    set (target, prop, value, receiver) {
      var obj = protos.find(obj => prop in obj);
      return Reflect.set(obj || Object.create(null), prop, value, receiver);
    },
    *enumerate (target) { yield* this.ownKeys(target); },
    ownKeys(target) {
      var hash = Object.create(null);
      for(var obj of protos) for(var p in obj) if(!hash[p]) hash[p] = true;
      return Object.getOwnPropertyNames(hash);
    },
    getOwnPropertyDescriptor(target, prop) {
      var obj = protos.find(obj => prop in obj);
      var desc = obj ? getDesc(obj, prop) : void 0;
      if(desc) desc.configurable = true;
      return desc;
    },
    preventExtensions: (target) => false,
    defineProperty: (target, prop, desc) => false,
  }));
}

Объяснение

Прокси-объект состоит из целевого объекта и некоторых ловушек, которые определяют пользовательское поведение для основных операций.

При создании объекта, который наследуется от другого, мы используем Object.create(obj). Но в этом случае мы хотим множественное наследование, поэтому вместо obj я использую прокси-сервер, который перенаправляет основные операции на соответствующий объект.

Я использую эти ловушки:

  • has trap является ловушкой для in оператор. Я использую some, чтобы проверить, содержит ли хотя бы один прототип свойство.
  • get trap является ловушкой для получения значений свойств. Я использую find, чтобы найти первый прототип, который содержит это свойство, и я возвращаю значение или вызываю геттер в соответствующем приемнике. Это обрабатывается Reflect.get. Если никакой прототип не содержит свойство, я возвращаю undefined.
  • set trap является ловушкой для установки значений свойств. Я использую find, чтобы найти первый прототип, который содержит это свойство, и я вызываю его сеттер на соответствующем приемнике. Если сеттер отсутствует или прототип не содержит свойства, значение определяется в соответствующем приемнике. Это обрабатывается Reflect.set.
  • enumerate trap является ловушкой для for...in циклов. Я перебираю перечислимые свойства из первого прототипа, затем из второго и т.д. Как только свойство было повторено, я сохраняю его в хеш-таблице, чтобы избежать повторения его снова.
    Предупреждение. Эта ловушка была удалена в черновик ES7 и устарела в браузерах.
  • ownKeys trap является ловушкой для Object.getOwnPropertyNames(). Поскольку циклы ES7, for...in продолжают вызывать [[GetPrototypeOf]] и получают собственные свойства каждого из них. Поэтому, чтобы заставить его перебирать свойства всех прототипов, я использую эту ловушку, чтобы сделать все перечислимые унаследованные свойства похожими на собственные свойства.
  • getOwnPropertyDescriptor trap является ловушкой для Object.getOwnPropertyDescriptor(). Сделать все перечисленные свойства похожими на собственные свойства в ловушке ownKeys недостаточно, for...in петли получат дескриптор, чтобы проверить, перечислимы ли они. Поэтому я использую find, чтобы найти первый прототип, который содержит это свойство, и я повторяю его прототипическую цепочку, пока не найду владельца собственности, и я верните его дескриптор. Если никакой прототип не содержит свойство, я возвращаю undefined. Дескриптор модифицирован, чтобы сделать его настраиваемым, иначе мы могли бы разбить некоторые прокси-инварианты.
  • preventExtensions и defineProperty ловушки включены только для предотвращения изменения этих операций прокси-сервера. В противном случае мы могли бы покончить с некоторыми прокси-инвариантами.

Доступно больше ловушек, которые я не использую

  • Можно добавить getPrototypeOf trap, но нет надлежащего способа вернуть несколько прототипов. Это означает, что instanceof тоже не будет работать. Поэтому я позволяю ему получить прототип цели, которая изначально имеет значение null.
  • setPrototypeOf trap можно добавить и принять массив объектов, который заменит прототипы. Это остается как упражнение для читателя. Здесь я просто позволю ему изменить прототип цели, что не очень полезно, потому что ни одна ловушка не использует цель.
  • deleteProperty trap является ловушкой для удаления собственных свойств. Прокси представляет собой наследование, поэтому это не имеет большого смысла. Я позволяю ему пытаться удалить на цель, которая в любом случае не должна иметь никакого свойства.
  • isExtensible trap - это ловушка для получения расширяемости. Не очень полезно, учитывая, что инвариант заставляет его возвращать ту же расширяемость, что и цель. Поэтому я просто позволю перенаправить операцию на цель, которая будет расширяемой.
  • apply и construct ловушки являются ловушками для вызова или создания экземпляров. Они полезны только тогда, когда целью является функция или конструктор.

Пример

// Creating objects
var o1, o2, o3,
    obj = multiInherit(o1={a:1}, o2={b:2}, o3={a:3, b:3});

// Checking property existences
'a' in obj; // true   (inherited from o1)
'b' in obj; // true   (inherited from o2)
'c' in obj; // false  (not found)

// Setting properties
obj.c = 3;

// Reading properties
obj.a; // 1           (inherited from o1)
obj.b; // 2           (inherited from o2)
obj.c; // 3           (own property)
obj.d; // undefined   (not found)

// The inheritance is "live"
obj.a; // 1           (inherited from o1)
delete o1.a;
obj.a; // 3           (inherited from o3)

// Property enumeration
for(var p in obj) p; // "c", "b", "a"

Ответ 3

Обновление (2019): оригинальный пост становится довольно устаревшим. Эта статья (теперь ссылка на интернет-архив, так как домен исчез) и связанная с ней библиотека GitHub - хороший современный подход.

Исходное сообщение: множественное наследование [править, не правильное наследование типа, но свойств; mixins] в Javascript довольно просто, если вы используете построенные прототипы, а не общие объекты. Вот два родительских класса для наследования:

function FoodPrototype() {
    this.eat = function () {
        console.log("Eating", this.name);
    };
}
function Food(name) {
    this.name = name;
}
Food.prototype = new FoodPrototype();


function PlantPrototype() {
    this.grow = function () {
        console.log("Growing", this.name);
    };
}
function Plant(name) {
    this.name = name;
}
Plant.prototype = new PlantPrototype();

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

Теперь нам просто нужен класс, который наследуется от обоих. Наследование осуществляется путем вызова функции конструктора (без использования ключевого слова new) для прототипов и конструкторов объектов. Во-первых, прототип должен наследоваться от родительских прототипов

function FoodPlantPrototype() {
    FoodPrototype.call(this);
    PlantPrototype.call(this);
    // plus a function of its own
    this.harvest = function () {
        console.log("harvest at", this.maturity);
    };
}

И конструктор должен наследовать от родительских конструкторов:

function FoodPlant(name, maturity) {
    Food.call(this, name);
    Plant.call(this, name);
    // plus a property of its own
    this.maturity = maturity;
}

FoodPlant.prototype = new FoodPlantPrototype();

Теперь вы можете выращивать, есть и собирать различные экземпляры:

var fp1 = new FoodPlant('Radish', 28);
var fp2 = new FoodPlant('Corn', 90);

fp1.grow();
fp2.grow();
fp1.harvest();
fp1.eat();
fp2.harvest();
fp2.eat();

Ответ 4

Этот метод использует Object.create для создания реальной прототипной цепи:

function makeChain(chains) {
  var c = Object.prototype;

  while(chains.length) {
    c = Object.create(c);
    $.extend(c, chains.pop()); // some function that does mixin
  }

  return c;
}

Например:

var obj = makeChain([{a:1}, {a: 2, b: 3}, {c: 4}]);

вернется:

a: 1
  a: 2
  b: 3
    c: 4
      <Object.prototype stuff>

чтобы obj.a === 1, obj.b === 3 и т.д.

Ответ 5

Мне нравится реализация функции класса Дж. Реджига: http://ejohn.org/blog/simple-javascript-inheritance/

Это может быть просто расширено:

Class.extend = function(prop /*, prop, prop, prop */) {
    for( var i=1, l=arguments.length; i<l; i++ ){
        prop = $.extend( prop, arguments[i] );
    }

    // same code
}

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


мой довольно запутанный пример выше доступен на https://github.com/cwolves/Fetch/blob/master/support/plugins/klass/klass.js

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


Если вы хотите скованное наследование (не множественное наследование, но для большинства людей это одно и то же), это может быть выполнено с помощью класса, например:

var newClass = Class.extend( cls1 ).extend( cls2 ).extend( cls3 )

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

Ответ 6

Не путайте с реализациями JavaScript с множественным наследованием.

Все, что вам нужно сделать, это использовать Object.create() для создания нового объекта каждый раз с указанным объектом и свойствами прототипа, тогда обязательно измените Object.prototype.constructor каждый шаг путь, если вы планируете создавать экземпляры B в будущем.

Чтобы наследовать свойства экземпляра thisA и thisB, мы используем Function.prototype.call() в конце каждой функции объекта. Это необязательно, если вы только заботитесь о наследовании прототипа.

Запустите следующий код где-нибудь и наблюдайте objC:

function A() {
  this.thisA = 4; // objC will contain this property
}

A.prototype.a = 2; // objC will contain this property

B.prototype = Object.create(A.prototype);
B.prototype.constructor = B;

function B() {
  this.thisB = 55; // objC will contain this property

  A.call(this);
}

B.prototype.b = 3; // objC will contain this property

C.prototype = Object.create(B.prototype);
C.prototype.constructor = C;

function C() {
  this.thisC = 123; // objC will contain this property

  B.call(this);
}

C.prototype.c = 2; // objC will contain this property

var objC = new C();
  • B наследует прототип от A
  • C наследует прототип от B
  • objC - это экземпляр C

Это хорошее объяснение вышеприведенных шагов:

OOP В JavaScript: что вам нужно знать

Ответ 7

Я никоим образом не специалист по javascript OOP, но если я правильно вас понял, вы хотите что-то вроде (псевдокода):

Earth.shape = 'round';
Animal.shape = 'random';

Cat inherit from (Earth, Animal);

Cat.shape = 'random' or 'round' depending on inheritance order;

В этом случае Id попробуйте что-то вроде:

var Earth = function(){};
Earth.prototype.shape = 'round';

var Animal = function(){};
Animal.prototype.shape = 'random';
Animal.prototype.head = true;

var Cat = function(){};

MultiInherit(Cat, Earth, Animal);

console.log(new Cat().shape); // yields "round", since I reversed the inheritance order
console.log(new Cat().head); // true

function MultiInherit() {
    var c = [].shift.call(arguments),
        len = arguments.length
    while(len--) {
        $.extend(c.prototype, new arguments[len]());
    }
}

Ответ 8

Возможно реализовать множественное наследование в JavaScript, хотя это делает очень мало библиотек.

Я мог бы указать Ring.js, единственный пример, который я знаю.

Ответ 9

Сегодня я много работал над этим и пытался добиться этого сам в ES6. То, как я это делал, это использовать Browserify, Babel, а затем я проверил его с Wallaby, и это, казалось, сработало. Моя цель - расширить текущий массив, включить ES6, ES7 и добавить некоторые дополнительные пользовательские функции, которые мне нужны в прототипе для работы с аудиоданными.

Уоллаби проходит 4 моих теста. Файл example.js можно вставить в консоль, и вы увидите, что свойство 'includes' находится в прототипе класса. Я все еще хочу проверить это завтра.

Здесь мой метод: (я скорее всего рефакторинг и переупаковка в качестве модуля после некоторого сна!)

var includes = require('./polyfills/includes');
var keys =  Object.getOwnPropertyNames(includes.prototype);
keys.shift();

class ArrayIncludesPollyfills extends Array {}

function inherit (...keys) {
  keys.map(function(key){
      ArrayIncludesPollyfills.prototype[key]= includes.prototype[key];
  });
}

inherit(keys);

module.exports = ArrayIncludesPollyfills

Github Repo: https://github.com/danieldram/array-includes-polyfill

Ответ 10

Я думаю, что это смехотворно просто. Проблема здесь в том, что дочерний класс относится только к instanceof для первого класса, который вы вызываете

https://jsfiddle.net/1033xzyt/19/

function Foo() {
  this.bar = 'bar';
  return this;
}
Foo.prototype.test = function(){return 1;}

function Bar() {
  this.bro = 'bro';
  return this;
}
Bar.prototype.test2 = function(){return 2;}

function Cool() {
  Foo.call(this);
  Bar.call(this);

  return this;
}

var combine = Object.create(Foo.prototype);
$.extend(combine, Object.create(Bar.prototype));

Cool.prototype = Object.create(combine);
Cool.prototype.constructor = Cool;

var cool = new Cool();

console.log(cool.test()); // 1
console.log(cool.test2()); //2
console.log(cool.bro) //bro
console.log(cool.bar) //bar
console.log(cool instanceof Foo); //true
console.log(cool instanceof Bar); //false

Ответ 11

Проверьте код ниже, который показывает поддержку множественного наследования. Сделано с использованием ПРОТОТИПНОГО НАСЛЕДОВАНИЯ

function A(name) {
    this.name = name;
}
A.prototype.setName = function (name) {

    this.name = name;
}

function B(age) {
    this.age = age;
}
B.prototype.setAge = function (age) {
    this.age = age;
}

function AB(name, age) {
    A.prototype.setName.call(this, name);
    B.prototype.setAge.call(this, age);
}

AB.prototype = Object.assign({}, Object.create(A.prototype), Object.create(B.prototype));

AB.prototype.toString = function () {
    return 'Name: ${this.name} has age: ${this.age}'
}

const a = new A("shivang");
const b = new B(32);
console.log(a.name);
console.log(b.age);
const ab = new AB("indu", 27);
console.log(ab.toString());

Ответ 12

Взгляните на пакет IeUnit.

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

Ответ 13

Вот пример цепочки прототипов с использованием функций конструктора:

function Lifeform () {             // 1st Constructor function
    this.isLifeform = true;
}

function Animal () {               // 2nd Constructor function
    this.isAnimal = true;
}
Animal.prototype = new Lifeform(); // Animal is a lifeform

function Mammal () {               // 3rd Constructor function
    this.isMammal = true;
}
Mammal.prototype = new Animal();   // Mammal is an animal

function Cat (species) {           // 4th Constructor function
    this.isCat = true;
    this.species = species
}
Cat.prototype = new Mammal();     // Cat is a mammal

В этой концепции используется определение Yehuda Katz для "класса для JavaScript:

... JavaScript-класс - это просто объект Function, который служит как конструктор плюс прикрепленный объект-прототип. (Источник: Гуру Каца)

В отличие от Object.create approach, когда классы построены таким образом, и мы хотим создать экземпляры класса, нам не нужно знать, от чего наследуется каждый "класс". Мы просто используем new.

// Make an instance object of the Cat "Class"
var tiger = new Cat("tiger");

console.log(tiger.isCat, tiger.isMammal, tiger.isAnimal, tiger.isLifeform);
// Outputs: true true true true

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

// Let say we have another instance, a special alien cat
var alienCat = new Cat("alien");
// We can define a property for the instance object and that will take 
// precendence over the value in the Mammal class (down the chain)
alienCat.isMammal = false;
// OR maybe all cats are mutated to be non-mammals
Cat.prototype.isMammal = false;
console.log(alienCat);

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

// All cats are mutated to be non-mammals
Cat.prototype.isMammal = false;
console.log(tiger, alienCat);

Я изначально написал часть этого с этим ответом.

Ответ 14

Опоздавший в сцене SimpleDeclare. Однако, имея дело с множественным наследованием, вы все равно получите копии исходных конструкторов. Это необходимость в Javascript...

Merc.

Ответ 15

Я бы использовал ds.oop. Это похоже на prototype.js и другие. делает многократное наследование очень легким и минималистичным. (только 2 или 3 kb) Также поддерживает некоторые другие опрятные функции, такие как интерфейсы и инъекции зависимостей

/*** multiple inheritance example ***********************************/

var Runner = ds.class({
    run: function() { console.log('I am running...'); }
});

var Walker = ds.class({
    walk: function() { console.log('I am walking...'); }
});

var Person = ds.class({
    inherits: [Runner, Walker],
    eat: function() { console.log('I am eating...'); }
});

var person = new Person();

person.run();
person.walk();
person.eat();

Ответ 16

Смешиваемая библиотека обеспечивает полную поддержку множественного наследования для JavaScript. Он обрабатывает цепочки вызовов конструктора и предоставляет полезные служебные методы.

Ответ 17

Как насчет этого, он реализует множественное наследование в JavaScript:

    class Car {
        constructor(brand) {
            this.carname = brand;
        }
        show() {
            return 'I have a ' + this.carname;
        }
    }

    class Asset {
        constructor(price) {
            this.price = price;
        }
        show() {
            return 'its estimated price is ' + this.price;
        }
    }

    class Model_i1 {        // extends Car and Asset (just a comment for ourselves)
        //
        constructor(brand, price, usefulness) {
            specialize_with(this, new Car(brand));
            specialize_with(this, new Asset(price));
            this.usefulness = usefulness;
        }
        show() {
            return Car.prototype.show.call(this) + ", " + Asset.prototype.show.call(this) + ", Model_i1";
        }
    }

    mycar = new Model_i1("Ford Mustang", "$100K", 16);
    document.getElementById("demo").innerHTML = mycar.show();

А вот код для служебной функции specialize_with():

function specialize_with(o, S) { for (var prop in S) { o[prop] = S[prop]; } }

Это настоящий код, который работает. Вы можете скопировать и вставить его в HTML файл, и попробуйте сами. Это работает.

Это усилия по внедрению MI в JavaScript. Не много кода, больше ноу-хау.

Пожалуйста, не стесняйтесь смотреть на мою полную статью об этом, https://github.com/latitov/OOP_MI_Ct_oPlus_in_JS