web-dev-qa-db-ja.com

JavaScriptの複数の継承/プロトタイプ

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

誰かがこれを試みたのか(またはそうでないのか)成功した​​かどうか、そしてどのようにそれを行ったかを知りたいだけです。

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

これが理論的にどのように可能であるかを示すために、プライマリチェーンの最後にセカンダリチェーンを接続することで実現できますが、これは以前のプロトタイプのすべてのインスタンスに影響し、それは私が望むものではありません。

考え?

123
devios1

Mixins をjavascriptで使用して、現時点で複数の継承を介しておそらく解決したい同じ目標を達成できます。

53
Jan Jongboom

プロキシオブジェクト を使用すると、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 operator のトラップです。 some を使用して、少なくとも1つのプロトタイプにプロパティが含まれているかどうかを確認します。
  • get trap は、プロパティ値を取得するためのトラップです。 find を使用して、そのプロパティを含む最初のプロトタイプを見つけ、値を返すか、適切なレシーバーでゲッターを呼び出します。これは Reflect.get によって処理されます。プロトタイプにプロパティが含まれていない場合、undefinedを返します。
  • set trap は、プロパティ値を設定するためのトラップです。 find を使用して、そのプロパティを含む最初のプロトタイプを見つけ、適切なレシーバーでそのセッターを呼び出します。セッターがない場合、またはプロパティを含むプロトタイプがない場合、値は適切なレシーバーで定義されます。これは Reflect.set によって処理されます。
  • enumerate trapfor...in loops のトラップです。最初のプロトタイプから列挙可能なプロパティを繰り返し、次に2番目のプロトタイプから繰り返します。プロパティが反復されると、ハッシュテーブルに保存して、再度反復しないようにします。
    警告:このトラップは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"
40
Oriol

更新(2019):元の投稿はかなり古くなっています。 この記事 (ドメインがなくなったため、現在はインターネットアーカイブリンク)とそれに関連するGitHubライブラリは、優れた最新のアプローチです。

元の投稿:多重継承[編集、型の適切な継承ではなく、プロパティの継承。ジェネリックオブジェクトのプロトタイプではなく構築されたプロトタイプを使用する場合、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();

各ケースで同じ「名前」メンバーを使用していることに注意してください。これは、「名前」の処理方法について親が同意しない場合に問題になる可能性があります。ただし、この場合は互換性があります(実際には冗長です)。

ここで、両方から継承するクラスが必要です。継承は、プロトタイプとオブジェクトコンストラクターのコンストラクター関数を(新しいキーワードを使用せずに)callすることによって行われます。まず、プロトタイプは親プロトタイプから継承する必要があります

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();
12
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
      <Object.prototype stuff>

obj.a === 1obj.b === 3など.

6
pimvdb

John Resigのクラス構造の実装が好きです: 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 )

元のプロトタイプチェーンは保持されますが、多くの無意味なコードが実行されます。

5
Mark Kahn

多重継承の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();
  • BAからプロトタイプを継承します
  • CBからプロトタイプを継承します
  • objCCのインスタンスです

これは上記の手順の良い説明です:

JavaScriptのOOP:知っておくべきこと

4
Dave

それはとてつもなくシンプルだと思います。ここでの問題は、子クラスは最初に呼び出すクラスの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
BarryBones41

今日私はこれに多く取り組んでおり、ES6でこれを自分で達成しようとしていました。私がやった方法は、Browserify、Babelを使用してから、Walabyでテストしたところ、うまくいくように見えました。私の目標は、現在のアレイを拡張し、ES6、ES7を含め、オーディオデータを処理するためのプロトタイプに必要なカスタム機能を追加することです。

Wallabyは私のテストの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リポジトリ: https://github.com/danieldram/array-includes-polyfill

2
Daniel Ram

私は決して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
David Hellsing

JavaScriptで複数の継承を実装することは可能ですが、実装しているライブラリはほとんどありません。

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

2
nicolas-van

ISが多重継承のサポートを示している以下のコードを確認してください。 PROTOTYPAL INHERITANCEを使用して完了

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());
1
Shivang Gupta

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
dss

シーンの後発者は SimpleDeclare です。ただし、複数の継承を処理する場合、元のコンストラクターのコピーになります。それはJavascriptの必需品です...

メルク。

0
Merc

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

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

この概念は、JavaScriptのYehuda Katzの "class"の定義を使用しています。

... 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);

私はもともとこの一部を this answer で書きました。

0
Luke

パッケージを見てください IeUnit

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

0
James