return Reflect.get(...arguments);
Reflect.get refers to the getter if any. The foo getter is given the proxied object, receiver (receiver === case1), for its this. Meaning, the get trap for the bar is called as well.
const case1 = new Proxy({
get foo() {
console.log("The foo getter", this);
return this.bar;
},
bar: 3
}, {
get(target, property, receiver) {
console.log("The Proxy get trap", ...arguments);
return Reflect.get(...arguments);
}
});
console.log(case1.foo);
> case1.foo
▶The Proxy get trap ▶{bar: 3} ▶foo ▶Proxy {bar: 3}
▶The foo getter ▶Proxy {bar: 3}
▶The Proxy get trap ▶{bar: 3} ▶bar ▶Proxy {bar: 3}
▶3
return target[property];
Using the unproxied object, target. This also triggers the foo getter, but notice: this for the foo getter is the unproxied object, target. The get trap for the bar is not called.
const case2 = new Proxy({
get foo() {
console.log("The foo getter", this);
return this.bar;
},
bar: 3
}, {
get(target, property, receiver) {
console.log("The Proxy get trap", ...arguments);
return target[property];
}
});
console.log(case2.foo);
> case2.foo
▶The Proxy get trap ▶{bar: 3} ▶foo ▶Proxy {bar: 3}
▶The foo getter ▶{bar: 3}
▶3
return receiver[property];
Using the proxied object, receiver (receiver === case3). receiver[property] refers to the get trap, not the getter, causing an infinity loop.
const case3 = new Proxy({
get foo() {
console.log("The foo getter", this);
return this.bar;
},
bar: 3
}, {
get(target, property, receiver) {
console.log("The Proxy get trap", ...arguments);
return receiver[property];
}
});
console.log(case3.foo);
> case3.foo
▶The Proxy get trap ▶{bar: 3} ▶foo ▶Proxy {bar: 3}
▶The Proxy get trap ▶{bar: 3} ▶foo ▶Proxy {bar: 3}
▶The Proxy get trap ▶{bar: 3} ▶foo ▶Proxy {bar: 3}
▶The Proxy get trap ▶{bar: 3} ▶foo ▶Proxy {bar: 3}
▶The Proxy get trap ▶{bar: 3} ▶foo ▶Proxy {bar: 3}
▶The Proxy get trap ▶{bar: 3} ▶foo ▶Proxy {bar: 3}
▶The Proxy get trap ▶{bar: 3} ▶foo ▶Proxy {bar: 3}
▶The Proxy get trap ▶{bar: 3} ▶foo ▶Proxy {bar: 3}
▶The Proxy get trap ▶{bar: 3} ▶foo ▶Proxy {bar: 3}
▶The Proxy get trap ▶{bar: 3} ▶foo ▶Proxy {bar: 3}
▶The Proxy get trap ▶{bar: 3} ▶foo ▶Proxy {bar: 3}
▶The Proxy get trap ▶{bar: 3} ▶foo ▶Proxy {bar: 3}
▶The Proxy get trap ▶{bar: 3} ▶foo ▶Proxy {bar: 3}
▶The Proxy get trap ▶{bar: 3} ▶foo ▶Proxy {bar: 3}
▶The Proxy get trap ▶{bar: 3} ▶foo ▶Proxy {bar: 3}
▶The Proxy get trap ▶{bar: 3} ▶foo ▶Proxy {bar: 3}
▶The Proxy get trap ▶{bar: 3} ▶foo ▶Proxy {bar: 3}
▶The Proxy get trap ▶{bar: 3} ▶foo ▶Proxy {bar: 3}
▶The Proxy get trap ▶{bar: 3} ▶foo ▶Proxy {bar: 3}
▶The Proxy get trap ▶{bar: 3} ▶foo ▶Proxy {bar: 3}
▶The Proxy get trap ▶{bar: 3} ▶foo ▶Proxy {bar: 3}
▶The Proxy get trap ▶{bar: 3} ▶foo ▶Proxy {bar: 3}
▶The Proxy get trap ▶{bar: 3} ▶foo ▶Proxy {bar: 3}
▶The Proxy get trap ▶{bar: 3} ▶foo ▶Proxy {bar: 3}
▶The Proxy get trap ▶{bar: 3} ▶foo ▶Proxy {bar: 3}
▶The Proxy get trap ▶{bar: 3} ▶foo ▶Proxy {bar: 3}
▶The Proxy get trap ▶{bar: 3} ▶foo ▶Proxy {bar: 3}
▶The Proxy get trap ▶{bar: 3} ▶foo ▶Proxy {bar: 3}
……
Uncaught RangeError: Maximum call stack size exceeded
Now you get it.
Which to use
Can't understand why I should use Reflect.get(obj, 'foo') instead of obj['foo']
While using the Reflect verbs is idiomatic for Proxy trap implementation, there's actually no should. It depends on your use case. If your target ("unproxied") object does not have getters or you're not insterested in what properties its getters are accessing ("secondary property accesses"), you might not need the fancy-looking Reflect. On the other hand, if you'd like to trigger the trap for all kinds of property accesses, primary or secondary, you would need Reflect.
For me, I always stick to return Reflect.get(...arguments);.