24

Can't understand why I should use Reflect.get(obj, 'foo') instead of obj['foo'], or why the first one is useful as we can do the same thing using the good and old object bracket notation. Can someone please elaborate?

var obj = {foo: 'bar'};
obj['foo'];
Reflect.get(obj, 'foo');
darksoulsong
  • 13,988
  • 14
  • 47
  • 90
  • *"The `Reflect.get` method allows you to get a property on an object. It is like the property accessor syntax as a function."* – That's pretty much it. It's the same thing, but as a function. Can come in handy in functional programming. – deceze Jun 19 '17 at 20:26
  • @deceze but you can already create a closure anywhere you might need such a function... – Jon Jun 19 '17 at 20:27
  • @Jon Sure, but this one's built-in! … Yeah, that's all I got for that as well. – deceze Jun 19 '17 at 20:28

2 Answers2

25

Well, a pedantic answer to your question would be that they are entirely different: a property accessor returns a reference to a property, while Reflect.get returns its value.

From the practical standpoint that doesn't make any difference since property references are always dereferenced on the right side.

One practical usage of Reflect.get would be with its third argument, which, when combined with a Proxy, can be used to create different "views" of the same data.

let numbersView = obj => new Proxy(obj, {
    get(target, key, receiver) {
        return receiver(target[key])
    }
});

let decimal = x => String(x);

let english = x => {
    if (x === 1) return 'one';
    if (x === 2) return 'two';

};

let v = numbersView({
    a: 1,
    b: 2
});

console.log(Reflect.get(v, 'a', decimal))
console.log(Reflect.get(v, 'a', english))

This example is a bit made-up, but you got the idea.

georg
  • 211,518
  • 52
  • 313
  • 390
20

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);.