1. ホーム
  2. javascript

[解決済み] JavaScriptにおける多重継承とプロトタイプ

2022-06-05 03:49:17

質問

JavaScriptで初歩的な多重継承を行う必要が出てきました。(私はこれが良いアイデアかどうかを議論するためにここにいるわけではないので、親切にもそれらのコメントはあなた自身の中に留めておいてください)。

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

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

これが理論的にどのように可能であるかを示すために、一次チェーンの端に二次チェーンを取り付けることによって達成することができますが、これはそれらの以前のプロトタイプのすべてのインスタンスに影響し、私が望むものではありません。

どうでしょうか?

どのように解決するのですか?

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 トラップ は、トラップとして in 演算子 . 私が使っているのは some を使って、少なくとも一つのプロトタイプがそのプロパティを含んでいるかどうかをチェックします。
  • get トラップ はプロパティ値を取得するためのトラップです。使うのは find を使って、そのプロパティを含む最初のプロトタイプを見つけ、その値を返すか、適切なレシーバーでゲッターを呼び出します。これを処理するのが Reflect.get . もしプロトタイプがそのプロパティを含んでいなければ、私は undefined .
  • set トラップ はプロパティ値を設定するためのトラップです。使用するのは find を使用して、そのプロパティを含む最初のプロトタイプを見つけ、適切なレシーバーでそのセッターを呼び出します。セッターがない場合や、そのプロパティを含むプロトタイプがない場合は、適切なレシーバーで値が定義されます。これを処理するのが Reflect.set .
  • enumerate トラップ は、トラップとして for...in ループ . 列挙可能なプロパティは、最初のプロトタイプから、次に2番目のプロトタイプから、というように反復しています。一度反復されたプロパティは、再度反復することを避けるためにハッシュテーブルに格納します。

    警告 : このトラップはES7ドラフトで削除され、ブラウザでは非推奨となっています。
  • ownKeys トラップ は、トラップとして Object.getOwnPropertyNames() . ES7 からは for...in ループは[[GetPrototypeOf]]を呼び続け、それぞれの独自プロパティを取得します。そこで、すべてのプロトタイプのプロパティを反復させるために、このトラップを使用して、すべての列挙可能な継承されたプロパティを自分のプロパティのように表示させるようにしています。
  • getOwnPropertyDescriptor トラップ は、トラップとして Object.getOwnPropertyDescriptor() . すべての列挙可能なプロパティを自身のプロパティのように表示させるために ownKeys トラップは十分ではありません。 for...in ループは、それらが列挙可能かどうかをチェックするために記述子を取得します。そこで、私は find を使って、そのプロパティを含む最初のプロトタイプを見つけ、プロパティの所有者を見つけるまでそのプロトタイプチェーンを繰り返し、そのディスクリプタを返します。そのプロパティを含むプロトタイプがない場合、私は undefined . 記述子は設定可能にするために変更されます。そうしないと、いくつかのプロキシ不変条件を破る可能性があります。
  • preventExtensions defineProperty トラップは、これらの操作によってプロキシのターゲットが変更されるのを防ぐためだけに 含まれています。そうでなければ、プロキシの不変条件を破ることになりかねません。

もっといろいろなトラップがありますが、私は使いません。

  • getPrototypeOf トラップ を追加することができますが、複数のプロトタイプを返す適切な方法はありません。これはつまり instanceof も動作しないことを意味します。そこで、ターゲットのプロトタイプを取得させるのだが、最初はnullである。
  • setPrototypeOf トラップ が追加され、プロトタイプを置き換えるオブジェクトの配列を受け入れることができます。これは読者のための練習として残されています。ここでは、私はそれがターゲットのプロトタイプを変更させるだけで、トラップはターゲットを使用しないので、あまり有用ではありません。
  • deleteProperty トラップ は、自分のプロパティを削除するためのトラップです。プロキシは継承を表すので、これはあまり意味がないでしょう。とにかくプロパティがないはずのターゲットで削除を試みさせました。
  • isExtensible トラップ は拡張性を取得するためのトラップです。不変量がターゲットと同じ拡張性を返すことを強制することを考えると、あまり有用ではない。なので、拡張性を持つであろうターゲットに操作をリダイレクトさせるだけにしている。
  • 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"