JavaScript Jungle: Who is the Proxy 
Tags: javascript, webdev, beginners, programming
Published: 2021-10-29 12:27:39

I remember one of my interview few years ago. I was asked: 

> Given an object and how can we track when some property is accessed or updated on that object. 

Example that was given by interviewer was as follows:


```js 
const obj = {name: 'Rick', planet: 'Earth'};

/**
* Case 1: Reading property:  
* Whenever I read any property
* it should print 
* GETTING value for <property_name> 
*/

obj.name; // GETTING value for name

/**
* Case 2: Setting property:  
* Whenever I set any property
* it should print 
* SETTING <property_name> = <new_value>
*/

 obj.planet = 'Mars' 
// SETTING planet = Mars 
```
I was like what!!! 

![Confused](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/scdw7irz7gg46r74e2y2.gif)


 But any how I said to my self: 

* Let's pass this object into a function  
* Function somehow enhance this object and return it. 

So I wrote: 

```js 
function enhance(obj) {
  // do something with obj to enhance
}
```
Interviewer asked me about my thought process on this? After thinking and remembering a lot 🤷🏼‍♂️ I said, I know we have to some how intercept the `read` and `write` operation on the `object`, but I am not sure how to do it on the fly. I know when we define properties using [Object.defineProperty](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty), we have some control on how that property will behave but the object is already defined so I am not sure. Deep inside I was still not sure 😂 I was still thinking 🙇🏻‍♂️: 

![Still thinking](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xlmphfmkkkg3gj4pbcio.gif)

Interviewer dropped me a hint that if I am using `Object.defineProperty` then I need to redefine the properties on the object. Then after some hit and trial I came up with this: 

![Tada](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4s4fh9y5nbgzlty43ulg.gif)

```js 
function enhance(plainObject) {
   const keys = Object.keys(plainObject);

  const storedObject = {};

  keys.forEach((key) => {
    storedObject[key] = plainObject[key];
    Object.defineProperty(plainObject, key, {
      get: function () {
        console.log(`GETTING value for  [${key}]`);
        return storedObject[key];
      },
      set: function (value) {
        console.log(`SETTING ${key} = ${value}`);
        storedObject[key] = value;
      }
    });
  });

  return plainObject;
}


let obj = { name: "rick", planet: "earth" };

const enhancedObj = enhance(obj);

console.clear();
console.log(enhancedObj.name);
// GETTING value for  [name] 
// rick 
enhancedObj.planet = "Mars";
// SETTING planet = Mars 

```

### 🌴 Explanation:

* As we know, we have to redefine the properties again. First thing we need to do is to stored all the existing keys in `keys` variable. 
* Defined a new object `storedObject`
* Then for each key we copied everything form `plainObject` to `storedObject` 
* Next we defined all the properties on `plainObject` again but this time we used `Object.defineProperty` to defined it. 
* WE have overwritten the [get](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty#description)  which is called when we `read` property of an object and [set](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty#description) which is called when we set a value to a property of an object.
* We put the appropriate log there. 
* Also whenever `set` is called we will store that value in `storedObject` and return from `storedObject` when `get` is called. 

During this time I was explaining my thought process to interviewer and I did a lot of hit and trial as it was hard for me to remember methods on `Object`.

**My solution has issues some issues:**

![Still Thinking](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/47i85p1az08mblcyc3qc.gif)

* If we add a new property on `enhancedObject` it will not be `track`ed. By the way, interviewer asked me this question as well 😆. But I could not come up with any solution back then. 😅

```js

....

let obj = { 
    name: "rick", 
    planet: "earth" 
};

const enhancedObj = enhance(obj);

// this will not be tracked
enhancedObj.age = 30; 

```

I was completely unaware that there is a better solution in `ES6`. After coming home when I researched, I found out a solution which is so much easy to implement as well as to understand. 

![Solution gif](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bwtmu9my5sqi8w1v3x0d.gif)

Before we jump into our solution, let's learn a bit about JavaScript Specification of `Object`. 

----
## 🌳 Internal Methods 

JavaScript [specification](https://tc39.es/ecma262/#sec-object-internal-methods-and-internal-slots) describes some lower level internal methods on `Object` like `[[Get]]`, `[[SET]]`, `[[VALUE]]`, `[[Writable]]`, `[[Enumerable]]` and `[[Configurable]]`. As per the specifications: 

> Each object in an `ECMAScript` engine is associated with a set of `internal methods` that defines its `runtime behaviour`.

**Point to note:** It defines `runtime behaviour` 

But we cannot directly modify the behaviour of the `Object` at runtime using this so called `Internal Methods` as we cannot access it directly. As per the specifications: 

> These internal methods are not part of the ECMAScript language. They are defined by this specification purely for expository purposes. 

There are some other internal methods as well. You can checkout full list [here](https://tc39.es/ecma262/#table-essential-internal-methods)
 
But in `ES6` we have a way to tap into these `Internal methods` at runtime.

----

## 🌳 Introducing Proxy

**Proxy** is a `middleman`. It does following: 
 
* It `wraps` another object.
* It `intercepts` all the operations related to `Internal Methods`. 
* It can handle these operations by its own or it can  
forward these operation to the `wrapped object`. 

![Proxy Image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/aczp3vboitss5dxpuo9g.jpeg)

----

### 🌴 Anatomy of Proxy

```js 

let proxy = new Proxy(target, handler)

```
`Proxy` is an inbuilt object that take two arguments: 

* **target**: An object that we need to wrap
* **handler**: An object that defines various methods corresponding to `Internal Methods` that we need to intercept at run time. 

Handler methods are often refer to as `traps` because it traps or intercept the `Internal method`.


![Proxy relation](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2m1z9k8h8e2hh4ue82md.jpeg)


**Example**

```js 

const character = {
   name: 'Rick',
   planet: 'Earth'
};

const proxy = new Proxy(character, {});

// update name 

proxy.name = 'morty';


console.log(character.name) // morty

```


> 👾 **GOTCHA** 👾 : If you do not define any handler function. `Proxy` will pass the all the operations to wrapped object as if it is not there at all.

### 🌴 Handler methods

For each of the `Internal Method` there is a handler method defined on the Proxy object. Some of them are: 

| Internal Method | Handler Method | Triggered On |
| ---- | ---- | ---- |
|[[Get]] |	get |	When reading a property |
|[[Set]] | 	set|	When writing a value to a property|
|[[HasProperty]]|	has|	When used with `in` operator|
|[[Delete]]|	deleteProperty	| When deleting a property with `delete` operator |
|[[Call]]	|apply	| When we do a `function` call|

You can refer to full list on [MDN Docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy#handler_functions) and [TC39 docs](https://tc39.es/ecma262/#table-proxy-handler-methods)


### 🌴 Invariants

There are certain condition attached to each of the handle methods. These condition must be fulfilled by the trap or handler methods. These are often referred as `Invariants`. You can read more on this in note section [here](https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots). 

As an example for **[[SET]]** Operation these are the invariants as per `TC39` docs: 

* The result of [[Set]] is a Boolean value.
* Cannot change the value of a property to be different from the value of the corresponding target object property if the corresponding target object property is a non-writable, non-configurable own data property.
* Cannot set the value of a property if the corresponding target object property is a non-configurable own accessor property that has undefined as its [[Set]] attribute.


### 🌴 [[SET]] Trap

If we set a trap for `[[SET]]` operation and then we can modify the input before setting on original object `name`


```js 

const character = {
  name: "Rick",
  planet: "Earth"
};

const proxy = new Proxy(character, {
/**
* [[SET]] trap
* target: original object 
*         that has been wrapped
* prop: Property that has been set
* value: value to set to the property
*/
  set: function (target, prop, value) {
    // modifying the value 
    const capitalName = value.toUpperCase();
    // setting the value to target
    target[prop] = capitalName;
    return true;
  }
});

// update name

proxy.name = "morty";


// Log is MORTY instead of morty
console.log(character.name); // MORTY

```

### 🌴 [[GET]] Trap

Same as `[[SET]]` trap we can set the `[[GET]]` trap. Suppose when we access a property we want to print the log `Getting <property_name>`. We can achieve that by using `[[GET]]` trap like this: 



```js

const character = {
  name: "Rick",
  planet: "Earth"
};

const proxy = new Proxy(character, {

/**
* [[GET]] trap
* target: original object 
*         that has been wrapped
* property: Property name 
*/

  get: function(target, property) {
    // printing the log before setting value
    console.log(`Getting: ${property} `);
    return target[property];
  }
});

const characterName = proxy.name;
console.log(characterName); 

// Getting: name  
// Rick 

```
----
## 🌳 Reflect

Before I jump to `Proxy` solution of the problem. There is also a sister object of `Proxy`, which is known as `Reflect`. As per [MDN docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect) 

> Reflect is a built-in object that provides methods for interceptable JavaScript operations. The methods are the same as those of proxy handlers. Reflect is not a function object, so it's not constructible.

Point to note here is 

* It has methods same as Proxy 
* It is not a function
* It is not constructible i.e you `cannot` use it like `new Reflect`

All the methods on Reflect are `static` so you can directly call them like 

* Reflect.get(...)
* Reflect.set(...)

### 🌴 Relationship between Reflect and Proxy: 

* All the methods that you can define on the `Proxy`, `Reflect` has a `same method` with `same argument`. 

* Reflect can invoke the `Internal Method` by using the methods defined on it. 


|Proxy method |	Reflect call|	Internal method|
|----|----|----|
|get(target, property, receiver) |	Reflect.get(target, property, receiver)|	[[Get]]|
|set(target, property, value, receiver)|	Reflect.set(target, property, value, receiver)	|[[Set]]|
|delete(target, property)|	Reflect.deleteProperty(target, property))|[[Delete]]|

You can check other methods of Reflect on [MDN Reflect Docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect#static_methods)


### 🌴 What do we need Reflect for

We know there are a lot of `Invariants` that we need to deal with when we trap some operation in Proxy and forward it to the original wrapped object. Remembering every rule can be hard. 

> We can simply use `Reflect` to forward any operation on original object and it will take care of all the Invariants

So now our `[[SET]]` and `[[GET]]` trap will change like this: 

```js
const character = {
  name: "Rick",
  planet: "Earth"
};

const proxy = new Proxy(character, {
  set: function (target, prop, value, receiver) {
    const capitalName = value.toUpperCase();
    return Reflect.set(target, prop, capitalName, receiver)
  },

  get: function(target, property, receiver) {
    console.log(`Getting: ${property} `);
    return Reflect.get(target, property, receiver);
  }
});

```
----
## 🌳 Solution to original Problem: 

With `Proxy` and `Reflect` now we can build our solution like this: 

![finally](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1ki3frtx4natt6zu3a32.gif)


```js 
 const proxyObject = function (obj) {
  return new Proxy(obj, {
    set: function (target, property, value, receiver) {
      console.log(`SETTING ${property} = ${value}`);
      return Reflect.set(target, property, value, receiver);
    },
    get: function (target, property, receiver) {
      console.log(`GETTING value for  [${property}]`);
      return Reflect.get(target, property, receiver);
    }
  });
};


let character = { name: "morty", planet: "earth" };

character = proxyObject(character);



console.log(character.name);
// GETTING value for  [name] 
// morty 
character.planet = "Mars";
// SETTING planet = Mars 


/**
* ES5 solution does not work if we set new properties
* ES6 Proxy will work for new properties as well and 
* print our log
*/

charter.grandParent = 'Rick';
// SETTING grandParent = Rick 

```
----
## 🍃 Browser Support for Proxy

As you can see most of latest browsers already support `Proxy` except IE, Baidu and Opera. So if you do not care about these three, you can use it like a breeze. 

![Proxy browser support](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/awzwsc50718pczyiv1q9.png)

----
## 🦾 Practical Usage


You might be thinking, hmmmmm... this ok but what is the practical usage of this. During my research for this article I came across an example of a JavaScript framework that is utilising the powers of Proxy and that frameworkkkkkk isssss....


* [Vue 3](https://v3.vuejs.org/) : `Vue 3` uses Proxy to be reactive and yes you got it right, `Vue 3` does not support IE 😝. Vue 3 uses `Proxy` for change detection and firing side effects. 

If you are not tired after reading my blog I will highly recommend you watch this [free video](https://www.vuemastery.com/courses/vue-3-reactivity/vue3-reactivity) to see **full potential** of `Proxy`.


You can play with my solution here


{% codesandbox  javascript-proxy-fork-wocuf %}

----

## 🌳 What to read next


{% link https://dev.to/xenoxdev/curious-case-of-sparse-array-in-js-38h7 %}

{% link https://dev.to/xenoxdev/javascript-jungle-convert-any-object-to-iterable-40l6 %}


Thank you for reading. 

Follow me on [twitter](https://twitter.com/VikasYadav_Dev)

## 🖖🏼 References 

* [T39 Docs](https://tc39.es/ecma262/#table-proxy-handler-methods)
* [MDN Docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy)
* [Getting Started with Modern JavaScript — Proxy](https://indepth.dev/posts/1361/getting-started-with-modern-javascript-proxy)
* [Human who codes article](https://humanwhocodes.com/blog/2014/04/29/creating-type-safe-properties-with-ecmascript-6-proxies/)
* [Vue3 Reactivity docs](https://v3.vuejs.org/guide/reactivity.html#what-is-reactivity)