Skip to content

Instantly share code, notes, and snippets.

@jelkand
Created July 29, 2019 22:27
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jelkand/6875ba7dbb4fd3e4c07af5bc27ed38e6 to your computer and use it in GitHub Desktop.
Save jelkand/6875ba7dbb4fd3e4c07af5bc27ed38e6 to your computer and use it in GitHub Desktop.
draft blog post

Metaprogramming with Reflect and Proxy in Javascript

Metaprogramming is a powerful tool in any language, and with the advent of ES6, there are even more options for metaprogramming in JavaScript now! ES6 added Reflect and Proxy to its repertoire of built-in objects, which allow for dynamic behavior in your code.

What is metaprogramming?

At its core, metaprogramming is simple—it just means programs that can write programs. Think of it as code that can change itself or other code while running.

As you could imagine, metaprogramming is very powerful, but also very dangerous.

Why would you use metaprogramming?

  1. Changing the behavior of code you don’t control—for example, adding logging, validation, or other utility code to a third party library.
  2. Making a program more flexible, for example, adding in backwards compatibility, or instituting a switch based on a variable set at runtime.

Why shouldn’t you use metaprogramming?

  1. Metaprogramming is a complicated solution. Simple solutions are better 95% of the time.
  2. Metaprogramming obfuscates the code flow—sometimes it’s hard to know what is being run just by looking at the code. It might take debugging and stepping through the code just to figure out what it does.
  3. You can’t rely on using ES6
  4. You need to run the code in Internet Explorer.

Reflect

Reflection is a crucial part of metaprogramming. It allows for access to an object and its methods and attributes at runtime.

For example, we might have some object const obj = { x: 1 };

Using conventional programming, we might run the following code:

console.log(obj.x);
obj.x = 2;
obj.someMethod();

Using Reflect we might run it this way:

console.log(Reflect.get(obj, 'x'));
Reflect.set(obj, 'x', 2);

Reflect has corresponding methods for any object access methods, as well as methods for calling functions as well.

If you’re thinking that that just looks more verbose and awkward, you’re right. Remember what we said about meta programming being a complicated solution? That said, there are some rare instances where it’s indispensable.

Cases where Reflect might be the best choice:

  • Use alongside the Proxy Object.
  • Cases where you might be setting or applying a dynamic attribute—where you don’t necessarily know what you’ll be asking for until runtime.

Proxy

The Proxy object is a way to intercept or ‘trap’ fundamental behavior on JavaScript objects, providing an entry point into that action to inspect or even redefine that behavior.

The proxy object takes two arguments. 1: the target, which is the object we want to inspect. 2: the handler which is a collection of traps. The supported traps include set, which intercepts behavior to change a property on the object, get, which intercepts access of a property, apply which will intercept function calls, and others.

If you’re thinking to yourself that Proxy methods share their names with a lot of Reflect methods, you’re exactly right. While there may be some subtle differences, Proxy and Reflect mirror each other closely.

Examples where Proxy is useful:

  • You need to augment existing behavior on an object—adding validation, modifying access to fields, etc.
  • You need to redirect code based on some variable that is determined at runtime.

Putting it together

Have you ever logged out the contents of an object in 47 different places in your code, trying to see where object.foo got changed from ’bar’ to ’baz’? Worse still, does that object get mutated in some third party code stashed away in node_modules? I know I have, at least twice in the last six months alone.

Here’s an example of a method using both Proxy and Reflect that will log changes to the fields of an object:

const attachProxy = (target) => {
  const handler = {
    set(obj, prop, value) {
      console.log('setting value of prop:', prop, 'to value:', value, 'obj', obj);
      console.trace();
      return Reflect.set(...arguments);
    }
  };
  return new Proxy(target, handler);
}

Note that it uses the set trap on Proxy to listen for any changes to properties, and then uses Reflect to actually apply those changes.

You can see how this might be useful in other cases—you could instead do validation in that handler, and only return the Reflection when validation passes. Since you can attachProxy to any object, you can even call it on third party code, like lodash . That said, you must be able to replace the object reference with a reference to the proxy, which is a very clear limitation.

Here’s the method in action:

// usage:
const obj = { foo: 'bar' };
const proxiedObj = attachProxy(obj);

proxiedObj.foo = 'baz';

/* output:
VM192:4 setting value of prop: foo to value: baz obj {foo: "bar"}
VM192:5 console.trace
set @ VM192:5
(anonymous) @ VM217:4
*/

Best of luck in your metaprogramming adventures with Reflect and Proxy in your toolkit!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment