Categories
Modern JavaScript

Best of Modern JavaScript — Proxies and Object Operations

Spread the love

Since 2015, JavaScript has improved immensely.

It’s much more pleasant to use it now than ever.

In this article, we’ll look at metaprogramming with JavaScript proxies.

Wrapping Instances of Built-in Constructors

We can use proxies to wrap instances of built-in constructors.

For instance, we can write:

const target = new Date();
const handler = {};
const proxy = new Proxy(target, handler);

But if we write:

console.log(proxy.getDate());

We get ‘Uncaught TypeError: this is not a Date object.’ because we called getDate on the proxy instead of the original Date instance.

However, proxies can be wrapped transparently with arrays.

For instance, we can write:

const p = new Proxy(new Array(), {});
p.push('foo');
console.log(p.length);

We created a proxy with the Array constructor.

The handler object is an empty object.

We call push with to insert an entry into the array.

It works with the proxy.

And we get the length with as we expected.

For objects that can’t be transparently wrapped with proxies, we can modify the handler so that it does what we expect.

For instance, we can write:

const handler = {
  get(target, propKey, receiver) {
    if (typeof target[propKey] === 'function') {
      return target[propKey].bind(target);
    }
    return target[propKey]
  },
};

const target = new Date();
const proxy = new Proxy(target, handler);

Then we call the target ‘s propKey method.

In the get method, we check if target[propKey] ‘s type is a 'function' .

If it is, then we change the value of this with bind so that the function will have the correct value of this .

Otherwise, we just return the value as-is.

Now we can call the getDate method on the object without issue:

const handler = {
  get(target, propKey, receiver) {
    if (typeof target[propKey] === 'function') {
      return target[propKey].bind(target);
    }
    return target[propKey]
  },
};

const target = new Date();
const proxy = new Proxy(target, handler);

console.log(proxy.getDate());

Use Cases for Proxies

We can use proxies to log the operations of the object.

For example, we can write:

const handler = {
  get(target, propKey, receiver) {
    console.log(target[propKey])
    if (typeof target[propKey] === 'function') {
      return target[propKey].bind(target);
    }
    return target[propKey]
  },
};

class Person {
  constructor(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
  }
  toString() {
    return `Person(${this.firstName}, ${this.lastName})`;
  }
}

const target = new Person('jane', 'smith');
const proxy = new Proxy(target, handler);

console.log(proxy.toString());

to trace the object operation.

We get the structure of the toString method.

And then we get the returned result with it.

We can also use proxies to warn about unknown properties.

For instance, we can write:

const target = {};
const handler = {
  get(target, propKey, receiver) {
    if (!(propKey in target)) {
      throw new Error('property does not exist');
    }
    return target[propKey];
  }
};

const proxy = new Proxy(target, handler);
console.log(proxy.foo);

We have a handler object that has a get method.

Inside it, we use the in operator to check if there’s a property with the name propKey .

Since there’s no foo property in target , we get ‘Uncaught Error: property does not exist’.

This is a handy feature that doesn’t in JavaScript objects natively.

Conclusion

We can use proxies to log object operations and intercept object operations in various ways.

Leave a Reply

Your email address will not be published.

If you like the content of this blog, subscribe to my email list to get exclusive articles not available to anyone else.