Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does the Proxy object reflect changes beyond the target object?

I wanted to experiment a bit with the Proxy object, and got some unexpected results, as follows:

Test script

function Person(first, last, age) {
  this.first = first;
  this.last = last;
  this.age = age;
}

Person.prototype.greeting = function () {
  return `Hello my name is ${this.first} and I am ${this.age} years old`;
};

So in tracking how the prototype object is being modified, I added this wrapper:

let validator = {
    set: function(target, key, value) {
        console.log(`The property ${key} has been updated with ${value}`);
        target[key] = value;
        return true;
    }
};

Person.prototype = new Proxy(Person.prototype, validator);

let george = new Person('George', 'Clooney', 55);

Person.prototype.farewell = function () {
  return `Hello my name is ${this.first} and I will see you later`;
};

What I expected

The property: "farewell" has been updated with: "function () {
  return `Hello my name is ${this.first} and I will see you later`;
}"

and nothing else.

And of course, each time I added or removed something from the prototype, i.e Person.prototype or instance.constructor.prototype I expected to see the console.log() message.

However I did not expect to see anything when setting something on the instance, like:

george.someProp = 'another value'; // did NOT expect to see the console.log()


Output

The property: "first" has been updated with: "george"
The property: "last" has been updated with: "clooney"
The property: "age" has been updated with: "55"
The property: "farewell" has been updated with: "function () {
  return `Hello my name is ${this.first} and I will see you later`;
}"
Person.prototype
Proxy {greeting: ƒ, first: "George", last: "Clooney", age: 55, farewell: ƒ, constructor: ƒ}

It set all the properties on the prototype and nothing on the instance, and each time I set something on the instance it sets it straight on the prototype.

Evidently this is not the default behaviour, as if I remove that Proxy, every property set with this will be set on the instance itself and the prototype will start up empty (or in our case with just the greeting function).

What am I missing here ? A point in the right direction would be appreciated.

like image 934
Marius Mucenicu Avatar asked Sep 26 '19 13:09

Marius Mucenicu


People also ask

What does a proxy do to the target object 1 point?

A JavaScript Proxy is an object that wraps another object (target) and intercepts the fundamental operations of the target object. The fundamental operations can be the property lookup, assignment, enumeration, and function invocations, etc.

What is proxy and reflect in JavaScript?

The Proxy and Reflect objects allow you to intercept and define custom behavior for fundamental language operations (e.g. property lookup, assignment, enumeration, function invocation, etc.). With the help of these two objects you are able to program at the meta level of JavaScript.

What is a proxy target?

target : the original object which you want to proxy. handler : an object that defines which operations will be intercepted and how to redefine intercepted operations.

What is the use of proxy in JavaScript?

In JavaScript, proxies (proxy object) are used to wrap an object and redefine various operations into the object such as reading, insertion, validation, etc. Proxy allows you to add custom behavior to an object or a function.


1 Answers

What you are missing is the fact that when you have a Proxy object in the prototype chain, the set handler will be called when you modify the child object.

In your example, when you set a property on the new instance, the set trap will be executed, the target will be the wrapped Person.prototype object, but there's a fourth argument, the receiver. This argument points to the object that the property has been accessed on.

To properly do the property assignment, you can use the Reflect.set API to set it:

Reflect.set(target, key, value, receiver);

That's why the Reflect API matches the proxy traps arguments.

So, in your example, we could use the Reflect API and you will see that Person.prototype doesn't get "polluted".

function Person(first, last, age) {
  this.first = first;
  this.last = last;
  this.age = age;
}

Person.prototype.greeting = function () {
  return `Hello my name is ${this.first} and I am ${this.age} years old`;
};


const validator = {
    set: function(target, key, value, receiver) {
        console.log(`The property ${key} has been updated with ${value}`);
        Reflect.set(target, key, value, receiver)
        return true;
    }
};

Person.prototype = new Proxy(Person.prototype, validator);

const george = new Person('George', 'Clooney', 55);

Person.prototype.farewell = function () {
  return `Hello my name is ${this.first} and I will see you later`;
};

console.log(george.hasOwnProperty('first')); // true
console.log(Person.prototype.hasOwnProperty('first')); // false
like image 125
Christian C. Salvadó Avatar answered Nov 09 '22 21:11

Christian C. Salvadó