JavaScriptの多重継承/プロトタイプ

私は、JavaScriptで何らかの初歩的な多重継承が必要なところに来ています。 (私はこれがいいアイデアかどうか議論するためにここにはいないので、親切にあなたにそれらのコメントを残してください。)

私はちょうど誰かがこれを試みたかどうかを知りたがっています。

私が本当に必要とするのは、複数のプロトタイプチェーン(つまり、それぞれのプロトタイプが独自の適切なチェーンを持つことができる)からプロパティを継承できるオブジェクトを持つことができるようにすることです。与えられた優先順位(最初の定義のためにチェーンを検索する)。

これが理論的にどのように可能であるかを実証するために、セカンダリチェーンをプライマリチェーンの端に取り付けることで実現できますが、これは以前のプロトタイプのすべてのインスタンスに影響します。

思考?

Edit Appreciate the responses folks, but while the consensus seems to be statically copying over the properties from both trees, which would work in most cases (and will probably be what I end up doing), I was most interested in a dynamic solution that would allow the separate prototype chains to be altered, and still have those changes "picked up" by the instance.

107
それは非常に動的ではないので、@ Pointy。私は親チェーンが発生したときにどちらかの変更を拾うことができるようにしたいと思います。しかし、それはちょうどそれが不可能な場合、私はこれに頼らなければならないかもしれないと言いました。
追加された 著者 devios1,
@TI afaik dojoは、C3スーパークラスの線形化アルゴリズムを使用して複数の継承をサポートします。しかし、内部的には多重継承のために渡された最初のクラスのみが真のスーパークラス(プロトタイプに基づく)です。残りはミックスインであり、継承チェーンを生成するために子クラスに混在しています。
追加された 著者 GibboK,
「私はこれがいいアイデアかどうか議論するためにここにいるわけではないので、あなた自身にそのコメントを残してください」 +1
追加された 著者 Dirk Boer,
TraitsJS(リンク1 リンク2 )これは、複数の継承とmixinsへの本当に良い選択肢です...
追加された 著者 CMS,
rel="nofollow noreferrer"> dojo宣言は複数の継承を処理すると考えています src また、私はmootoolsもやっていると感じています。道場が示唆しているように、これを素早く読んでいきます
追加された 著者 T I,
これに関する興味深い記事: webreflection.blogspot。 co.uk/2009/06/…
追加された 著者 Nobita,

14 答え

Mixins can be used in JavaScript to achieve the same goal you probably want to solve via multiple inheritance at the moment.

50
追加された
+1は素晴らしいリンクです。
追加された 著者 devios1,
Noble Mushtakの例は、私が今そこで見つけることができるものよりも複数の継承に最も近いものです。 mixinsもWeb Reflectionポストも、スーパーオブジェクトのプロトタイプの変更を子オブジェクトが拾う方法を提供しません。 Nobleの例では、getterを使用して、代わりに継承されたスーパーオブジェクトのプロパティを常に検索します。
追加された 著者 Spencer Williams,
名前の衝突などを助けるライブラリに興味がある場合は、 traitsjs .github.io/traits.js-website
追加された 著者 Matt Browne,
私は、複数の継承のためにmixinの代わりに Object.defineProperties()を使用するのが好きです: personalwebsite-noblehmushtak.rhcloud.com/blog/…
追加された 著者 Noble Mushtak,

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 の代わりに、基本操作を適切なオブジェクトにリダイレクトするプロキシを使用します。

私はこれらのトラップを使用します:

  • The has trap is a trap for the in operator. I use some to check if at least one prototype contains the property.
  • The get trap is a trap for getting property values. I use find to find the first prototype which contains that property, and I return the value, or call the getter on the appropriate receiver. This is handled by Reflect.get. If no prototype contains the property, I return undefined.
  • The set trap is a trap for setting property values. I use find to find the first prototype which contains that property, and I call its setter on the appropriate receiver. If there is no setter or no prototype contains the property, the value is defined on the appropriate receiver. This is handled by Reflect.set.
  • The enumerate trap is a trap for for...in loops. I iterate the enumerable properties from the first prototype, then from the second, and so on. Once a property has been iterated, I store it in a hash table to avoid iterating it again.
    Warning: This trap has been removed in ES7 draft and is deprecated in browsers.
  • The ownKeys trap is a trap for Object.getOwnPropertyNames(). Since ES7, for...in loops keep calling [[GetPrototypeOf]] and getting the own properties of each one. So in order to make it iterate the properties of all prototypes, I use this trap to make all enumerable inherited properties appear like own properties.
  • The getOwnPropertyDescriptor trap is a trap for Object.getOwnPropertyDescriptor(). Making all enumerable properties appear like own properties in the ownKeys trap is not enough, for...in loops will get the descriptor to check if they are enumerable. So I use find to find the first prototype which contains that property, and I iterate its prototypical chain until I find the property owner, and I return its descriptor. If no prototype contains the property, I return undefined. The descriptor is modified to make it configurable, otherwise we could break some proxy invariants.
  • The preventExtensions and defineProperty traps are only included to prevent these operations from modifying the proxy target. Otherwise we could end up breaking some proxy invariants.

利用可能なトラップがほかにありますが、私は使用しません

  • getPrototypeOf trap を追加することはできますが、複数のプロトタイプを返す適切な方法はありません。つまり、 instanceof はどちらもうまく動作しません。したがって、私はターゲットのプロトタイプを取得させました。最初はnullです。
  • setPrototypeOf trap を追加してプロトタイプを置き換えるオブジェクトの配列を受け入れることができます。これは読者のための運動として残されています。ここでは、ターゲットのプロトタイプを修正するようにしましたが、トラップがターゲットを使用しないためあまり役に立ちません。
  • deleteProperty trap は、独自のプロパティを削除するためのトラップです。プロキシは継承を表しているので、これはあまり意味がありません。とにかくプロパティを持たないターゲット上で削除を試みます。
  • isExtensible trap は、拡張性を得るためのトラップです。不変量がターゲットと同じ拡張性を返すように強制すると、それほど有用ではありません。だから私は、ターゲットに操作をリダイレクトさせて、拡張可能にするだけです。
  • apply および < code> 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"
29
追加された
パフォーマンスについて、複数のオブジェクトから継承するオブジェクトを作成し、複数のオブジェクトから継承するなど、指数関数的になります。そう、それは遅くなるでしょう。しかし、通常の場合、私はそれが悪いとは思わない。
追加された 著者 Oriol,
@TomášZato通常のオブジェクトのデータプロパティよりも処理速度が遅くなりますが、アクセサプロパティよりはるかに悪くはないと思います。
追加された 著者 Oriol,
通常の規模のアプリケーションでも関連するようなパフォーマンス上の問題はありませんか?
追加された 著者 Tomáš Zato,
私は何が起こっているのより良いアイデアを得るために "複数の委任"によって "複数の継承"を置き換えることを検討します。実装での重要な概念は、プロキシが実際にメッセージを委譲する (または転送する)ための適切なオブジェクトを選択していることです。ソリューションの力は、ターゲットプロトタイプを動的に拡張できることです。他の答えは、連結(ala Object.assign )を使用しているか、まったく異なるグラフを取得していて、最終的にはオブジェクト間にプロトタイプ・チェーンが1つしかないということです。プロキシソリューションはランタイムブランチングを提供し、これは驚異的です!
追加された 著者 sminutoli,
TIL: multiInherit(o1 = {a:1}、o2 = {b:2}、o3 = {a:3、b:3})
追加された 著者 bloodyKnuckles,

多重継承[編集、型の適切な継承ではなく、プロパティの継承。ジェネリックオブジェクトではなく、構築されたプロトタイプを使用すれば、Javascriptの「mixins」はかなり簡単です。継承する親クラスは次の2つです。

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();
7
追加された
組み込みのプロトタイプでこれを行うことはできますか? (配列、文字列、数値)
追加された 著者 Tomáš Zato,
まあ、私は Array.call(...)を行うことができますが、 this として渡すものには影響しないようです。
追加された 著者 Tomáš Zato,
@TomášZatoあなたは Array.prototype.constructor.call()を実行できます
追加された 著者 Roy J,
組み込みのプロトタイプには、呼び出すことができるコンストラクタがないと思います。
追加された 著者 Roy J,

これは実際のプロトタイプチェーンを作るために 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
      

obj.a === 1 obj.b === 3 などのように

5
追加された
簡単な仮説的な質問:NumberとArrayのプロトタイプを混ぜ合わせてVectorクラスを作ってみたかった。これは私に配列インデックスと数学演算子の両方を与えるでしょう。しかしそれは働くだろうか?
追加された 著者 Tomáš Zato,
@TomášZato、これをチェックする価値があるあなたが配列のサブクラス化を検討しているなら、記事を読んでください。頭痛を軽減することができます。がんばろう!
追加された 著者 user3276552,

I like John Resig's implementation of a class structure: http://ejohn.org/blog/simple-javascript-inheritance/

これは単に次のようなものに拡張することができます:

Class.extend = function(prop /*, prop, prop, prop */) {
    for( var i=1, l=arguments.length; i

継承する複数のオブジェクトを渡すことができます。ここでは instanceOf 機能を失うことになりますが、複数の継承が必要な場合はこれが指定されています。


my rather convoluted example of the above is available at https://github.com/cwolves/Fetch/blob/master/support/plugins/klass/klass.js

このファイルにはデッドコードがあることに注意してください。ただし、見たい場合は複数の継承が可能です。


連鎖継承が必要な場合(多重継承ではなく、ほとんどの人にとっては同じことです)、Classを使って次のようにすることができます:

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

オリジナルのプロトタイプチェーンを保持しますが、無意味なコードをたくさん実行することになります。

5
追加された
これにより、結合されたシャロークローンが作成されます。継承されたオブジェクトに新しいプロパティを追加しても、実際のプロトタイプの継承と同様に、派生オブジェクトに新しいプロパティが表示されるわけではありません。
追加された 著者 Daniel Earwicker,
あなたのGitHUbがなくなったようですが、あなたはまだ githubを持っていますか? com/cwolves/Fetch/blob/master/support/plugins/klass /&hellip; あなたが共有したいのであれば見ても大丈夫ですか?
追加された 著者 JasonDavis,
@DanielEarwicker - 真ですが、あるクラスが2つのクラスから派生しているという点で "多重継承"を望むなら、本当に別のクラスはありません。ほとんどの場合、単にクラスを連鎖させることを反映する修正された答えは同じことです。
追加された 著者 Mark Kahn,

JavaScriptフレームワークの多重継承実装と混同しないでください。

All you need to do is use Object.create() to create a new object each time with the specified prototype object and properties, then be sure to change the Object.prototype.constructor each step of the way if you plan on instantiating B in the future.

To inherit instance properties thisA and thisB we use Function.prototype.call() at the end of each object function. This is optional if you only care about inheriting the prototype.

次のコードをどこかで実行し、 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 inherits the prototype from A
  • C inherits the prototype from B
  • objC is an instance of C

これは上記のステップの良い説明です:

OOP In JavaScript: What You NEED to Know

3
追加された

私はjavascriptのOOPの専門家ではありませんが、私が正しく理解すれば、あなたは擬似コードのようなものを望みます:

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

Cat inherit from (Earth, Animal);

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

その場合、私は次のようなものを試してみます:

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]());
    }
}
2
追加された
これはちょうど最初のプロトタイプを選んで残りを無視していないのですか? c.prototype を複数回設定しても、複数のプロトタイプは生成されません。たとえば、 Animal.isAlive = true の場合、 Cat.isAlive は未定義です。
追加された 著者 devios1,
さて、私はプロトタイプを混ぜることを意味していました...(私はjQueryを使っていましたが、あなたは写真を手に入れました)
追加された 著者 David,

私は今日これを多く取り組んでいて、ES6で自分自身でこれを達成しようとしていました。私がやったやり方は、Browserify、Babelを使っていたのですが、それからWallabyでテストしたところ、うまくいきました。私の目標は、現在のArrayを拡張し、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

2
追加された

JavaScriptで多重継承を実装することは可能ですが、ライブラリはほとんどありません。

私が知っている唯一の例である Ring.js を指すことができました。

2
追加された
それは価値のある読書のように見える、リンクのおかげで!
追加された 著者 devios1,

私はそれがばかげて簡単だと思う。ここでの問題は、子クラスが呼び出す最初のクラスの 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
2
追加された

シーンの後者は SimpleDeclare です。しかし、複数の継承を扱う場合、元のコンストラクタのコピーが残ってしまいます。それはJavascriptの必需品です...

メル。

0
追加された
プロキシは面白いです!私は間違いなくSimpleDeclareを変更して、それらが標準の一部になったらプロキシを使用してメソッドをコピーする必要がないようにします。 SimpleDeclareのコードは、本当に読みやすく、変更するのが本当に簡単です...
追加された 著者 Merc,
それはJavascriptの必需品... ES6プロキシまで。
追加された 著者 Jonathon,

コンストラクタ関数を使用したプロトタイプチェーンの例を次に示します。

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オブジェクトです。 (出典:Guru Katz

Object.createのアプローチとは異なり、このようにクラスを作成し、 「クラス」のインスタンスでは、各「クラス」が継承しているものを知る必要はありません。 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's 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);

私はもともと、この回答でこれを書きました。

0
追加された
OPは複数のプロトタイプチェーンを要求しています( child parent1 parent2 から継承します)。あなたの例では、1つのチェーンについてしか話しません。
追加された 著者 poshest,

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();
0
追加された

パッケージ IeUnit をご覧ください。

IeUnitで実装された概念の同化は、あなたが探しているものを非常に力強い方法で提供するようです。

0
追加された
JavaScript - 日本のコミュニティ
JavaScript - 日本のコミュニティ
2 参加者の

日本人コミュニティのjavascript