JavaScript で何らかの基本的な多重継承が必要になるところまで来ました。(これが良いアイデアかどうかを議論するためにここにいるわけではないので、そのコメントは控えてください。)
誰かがこれを試みて成功したか(あるいは失敗したか)どうやったかを知りたいだけです。
要するに、私が本当に必要としているのは、複数のプロトタイプからプロパティを継承できるオブジェクトを持つことです。鎖(つまり、各プロトタイプには独自の適切なチェーンがある可能性があります) が、優先順位が指定されています (最初の定義の順にチェーンを検索します)。
これが理論的にどのように可能であるかを示すために、セカンダリ チェーンをプライマリ チェーンの端に接続することで実現できますが、これは以前のプロトタイプのすべてのインスタンスに影響するため、これは望んでいることではありません。
ご意見は?
ベストアンサー1
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
少なくとも 1 つのプロトタイプにプロパティが含まれているかどうかを確認します。 - の
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"