My friend sent this picture to me, shows the function add
that can chain numbers indefinitely, then output the sum.
I was thinking of using Proxy
to add key numbers together and rewrite its Symbol.toPrimitive
function, but it seems it doesn't work and I'm not sure what is going on...
Am I on the right direction or is there a better way to do it?
let add = new Proxy(
{
[Symbol.toPrimitive]() {
return this.value;
},
value: 0
},
{
get(target, key, receiver) {
if(Symbol.toPrimitive === key) {
return target[Symbol.toPrimitive];
} else if (!isNaN(key)) {
target.value += +key;
}
return add;
},
}
);
console.log(+add[1]);
console.log(+add[1][2][3]);
console.log(+add[10][-5][3][100]);
Your problem is that this
inside of your [Symbol.toPrimitive](){}
method is your proxy, and not your target
object (ie: your first object argument to the proxy constructor). This causes your proxy get trap to trigger, which results in your toPrimitive
method not returning a primitive, but instead an object (and so your code throws). This is because, when you perform:
console.log(+add[1]);
you are converting the Proxy (add[1]
) to a primitive number value. When this occurs, JavaScript will try and grab the Symbol.toPrimitive
function from that proxy. When that occurs, your get
trap in your handler object runs, and returns the Symbol.toPrimitive
function object defined on your target object. The engine then invokes this returned function with the this
value set to the proxy (not the handler object). In code, you can think of the following occurring when JS converts your proxy, add[1]
, to a number:
const input = add[1]; // your proxy
const exoticToPrim = input[Symbol.toPrimitive]; // gets the Symbol.toPrimitive function from your object
const result = exoticToPrim.call(input, "number"); // !! calls `Symbol.toPrimitive` !!
The above steps are outlined in the spec here. As you can see from the last line, your Symbol.toPrimitive()
function is invoked with this
set as your proxy value, which results in the following code also causing your get trap to fire:
[Symbol.toPrimitive]() {
return this.value;
}
above, this.value
triggers your get
method in your handler object to fire with a key of value
, as this
represents your proxy, causing the get trap to trigger. Because your get trap returns a proxy when key
is set to value
, your Symbol.toPrimitive
method doesn't return a primitive, but rather it returns your app
proxy, which causes your code to throw. A simple quick fix is to handle the case when value
is accessed on your object (note that I've also reset value
so that each log doesn't accumulate from the previous):
let add = new Proxy(
{
[Symbol.toPrimitive]() {
return this.value;
},
value: 0
},
{
get(target, key, receiver) {
if(key === Symbol.toPrimitive) {
return target[key];
} else if(key === 'value') {
const sum = target[key];
target[key] = 0;
return sum;
} else if (!isNaN(key)) {
target.value += +key;
}
return add;
},
}
);
console.log(+add[1]);
console.log(+add[1][2][3]);
console.log(+add[10][-5][3][100]);
Another option is to change the toPrimitive
function that gets invoked, which you can do by returning a function that wraps your toPrimitive
function and invokes it with a new this
value that is set to the target
object. You can also reset your value count in this new wrapper function:
let add = new Proxy(
{
[Symbol.toPrimitive]() {
return this.value;
},
value: 0
},
{
get(target, key, receiver) {
if(key === Symbol.toPrimitive) {
return (...args) => {
const prim = target[key].apply(target, args);
target.value = 0;
return prim;
};
} else if (!isNaN(key)) {
target.value += +key;
}
return add;
},
}
);
console.log(+add[1]);
console.log(+add[1][2][3]);
console.log(+add[10][-5][3][100]);