Proxy と __proto__
- カテゴリ:
- JavaScript
- コメント数:
- Comments: 0
◆ __proto__ は各オブジェクトのプロパティには存在しない
◆ Object.prototype に存在する getter と setter 付きプロパティ
◆ 何かのオブジェクトの __proto__ に Proxy オブジェクトを設定するとき __proto__ の考慮が必要
◆ __proto__ へのアクセスでも他と同じように getter/setter の処理結果になって思い通りの結果にならないこともある
◆ __proto__ のプロパティアクセスじゃなく Object.getPrototypeOf だと Proxy オブジェクトであってもチェインの先が取得できる
◆ Proxy オブジェクトに Object.getPrototypeOf を使うと 第一引数で設定したオブジェクトのプロトタイプチェーンの先が取得できる
◆ Object.prototype に存在する getter と setter 付きプロパティ
◆ 何かのオブジェクトの __proto__ に Proxy オブジェクトを設定するとき __proto__ の考慮が必要
◆ __proto__ へのアクセスでも他と同じように getter/setter の処理結果になって思い通りの結果にならないこともある
◆ __proto__ のプロパティアクセスじゃなく Object.getPrototypeOf だと Proxy オブジェクトであってもチェインの先が取得できる
◆ Proxy オブジェクトに Object.getPrototypeOf を使うと 第一引数で設定したオブジェクトのプロトタイプチェーンの先が取得できる
__proto__ はどこに?
オブジェクトにプロパティがあれば それを使ってなければ変わりのオブジェクトを探すプロキシを作った時のことですシンプルにするとこういうのです
ここまでシンプルになると __proto__ の機能と同じようなものですね
function proxy(obj, default_obj = {}) {
return new Proxy(obj, {
get: function(target, name, receiver) {
if (target.hasOwnProperty(name)) {
return target[name]
} else {
return default_obj[name]
}
},
set: function(target, name, value) {
target[name] = value
},
})
}
これを使って __proto__ を参照すると
const p = proxy([1, 2])
console.log(p[1])
// 2
console.log(p.__proto__ === Array.prototype)
// false
console.log(p.__proto__ === Object.prototype)
// true
Array のインスタンスの __proto__ なのに配列じゃなく Object のプロトタイプになっています
調べてみると __proto__ はすべてのオブジェクトに存在するプロパティではありませんでした
function X() {}
new X().hasOwnProperty("__proto__")
// false
Object.prototype.hasOwnProperty("__proto__")
// true
Object.getOwnPropertyDescriptor(Object.prototype, "__proto__")
// {get: ƒ, set: ƒ, enumerable: false, configurable: true}
すべてのオブジェクトの元のプロトタイプである Object.prototype で getter/setter として定義されていました
configuable なので 動きを変えることもできそうです
__proto__ に Proxy
Proxy オブジェクトを __proto__ に設定したときのことですconst p = new Proxy({}, {
get: function(target, name, receiver) {
return "PROXY"
},
})
const obj = {}
obj.prop = p
console.log(obj.prop === p)
// true
obj.__proto__ = p
console.log(obj.__proto__ === p)
// false
こういう現象が起きます
通常のプロパティでは ちゃんと代入したものと比べたら true です
ですが __proto__ では false になります
__proto__ でも通常は true になるはずです
const obj2 = {}
const obj_proto = {}
obj2.__proto__ = obj_proto
console.log(obj2.__proto__ === obj_proto)
// true
obj.__proto__ を見てみると
obj.__proto__
// "PROXY"
Proxy のゲッターに設定した PROXY という文字になっています
最初はよくわからなかったのですが __proto__ が Object.prototype にのみ存在するものとわかれば納得できます
obj 自体に __proto__ プロパティは存在しないのでプロトタイプチェーンの先の p オブジェクトの __proto__ プロパティを見ようとして getter 関数が呼び出されて PROXY が返されます
p の getter で name を表示してみると __proto__ と表示されています
この場合に obj の __proto__ (この場合は p) を取得するには Object.getPrototypeOf を使う必要があります
Object.getPrototypeOf(obj)
// Proxy { }
Proxy 側で対応
__proto__ を使いたいなら Proxy 側で __proto__ を対応させてしまうこともできますconst p = new Proxy({}, {
get: function(...a) {
if (a[1] === "__proto__") {
return Reflect.get(...a)
}
return "PROXY"
},
})
const obj = {}
obj.__proto__ = p
console.log(obj.__proto__)
// Proxy { }
console.log(obj.__proto__.__proto__)
// Object { }
console.log(obj.__proto__.__proto__.__proto__)
// null
Proxy の Object.getPrototypeOf
Proxy を使うときってたいてい 関数で Proxy オブジェクトを作るので Proxy の第一引数に渡すオブジェクトってなくていいことがほとんどですfunction createXXXProxy(obj) {
return new Proxy({}, {
get(target, name, reciver) {
return obj[name]
},
})
}
getter などの関数の第一引数 (target) に Proxy の第一引数に渡したオブジェクトの参照が渡されるのですが そんなことしなくても target に設定したいオブジェクトは参照できます
もし Proxy がこういう getter/setter などを設定するオプションのみの使い方だったとしても困りません
function proxy(option) {
return new Proxy({}, option)
}
const obj = { a: 1 }
const p = proxy({
get(target, name, receiver) {
console.log(name)
return obj[name]
},
})
console.log(p.a)
// a
// 1
なので普段は とりあえず {} か 関数呼び出しするなら function(){} を指定してました
ですが 今回 __proto__ を調べてるとちゃんと 第一引数を使ったほうが良いケースを見つけました
Object.getPrototypeOf を Proxy のインスタンスに対して使うと 第一引数で指定したオブジェクトの __proto__ が取れます
Object.getPrototypeOf(
new Proxy(new Date(), { get() { return null } })
).constructor
// ƒ Date() { [native code] }
Object.getPrototypeOf(
new Proxy(document.createElement("div"), { get() { return null } })
).constructor
// ƒ HTMLDivElement() { [native code] }
Object.getPrototypeOf(
new Proxy([], { get() { return null } })
).constructor
// ƒ Array() { [native code] }
__proto__ を使った場合の動きはこれまでに書いたように Proxy の定義次第です
new Proxy(new Date(), { get() { return null } }).__proto__
// null
こっちはちゃんと作るなら 上に書いたように Proxy のベースにするオブジェクトの __proto__ を取得できるようにすると思いますが それだけでは Object.getPrototypeOf の方は対応できません
動きを合わせるためにも 面倒でも第一引数にベースにするオブジェクトを渡して置くほうがいいのかもしれません