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"