Casper Beyer Self-proclaimed developer advocate, hate slow software. Grew up with C, work with JavaScript and a fan of Go.

Terrible use cases for JavaScript proxies

3 min read 866

ECMAScript 6 introduced a number of new language features to JavaScript, amongst them were proxies. Which are, in my opinion, the most underrated feature of JavaScript.

Proxies enable us to do runtime meta-programming by allowing us to intercept and redefine the behaviour for intrinsic operations such as property getters, setters, value assignments, call operations and so on.

Now the actual, real-world, practical good use cases for proxies are few and far between. In most cases, the same thing can be achieved with a bit of repetitive boilerplate code with far better performance. Still, proxies are great and incredibly powerful. Let’s have a look at some terrible use cases to show just how magical proxies can be.

Forgiving property names

One of the operations we can override is an object’s property getter. So let’s use that to provide an auto-correcting property lookup using the Levenshtein distance to approximate what the user’s intended property name was.

First things first, we need to define a function to return the Levenshtein distance between two strings. The Levenshtein distance is essentially a measurement of the minimum number of single-character edits (insertions, deletions or substitutions) required to change one string into the other.

We’ll do the recursive variant because it’s straightforward and easier to follow than a more optimized one. However, it should be noted that it’s also extremely inefficient compared to an iterative approach with lookup tables:

function levenshtein(a, b) {
  if (a.length == 0) {
    return b.length;

  if (b.length == 0) {
    return a.length;

  let cost = (a.charAt(a.length - 1) == b.charAt(b.length - 1)) ? 0 : 1;

  return Math.min(
    levenshtein(a.substring(0, a.length - 1), b) + 1,
    levenshtein(a, b.substring(0, b.length - 1)) + 1,
    levenshtein(a.substring(0, a.length - 1), b.substring(0, b.length - 1)) + cost,

With the Levenshtein distance figured out, it’s fairly trivial to get the closest matching property name by reducing an array of property names to the string with the shortest distance to the target property:

function getClosestPropertyName(names, name) {
  let lowest = Infinity;

  return names.reduce(function(previous, current) {
    let distance = levenshtein(current, name);
    if (distance < lowest) {
      lowest = distance;
      return current;

    return previous;
  }, '');

Finally moving on to the actual proxy object, proxies are defined as objects with a target object and a handler object. The target is the object which is virtualized by the proxy and the handler is an object whose properties are traps, or functions that define the behaviour of a proxy when an operation is done to it.

So to make an object’s properties be “autocorrected” we’ll define a function that takes the target as a parameter and returns a proxy which re-defines the get trap:

function autoCorrect(target, recursive) {
  return new Proxy(target, {
    get: function(target, name) {
      if (!(name in target)) {
        name = getClosestPropertyName(Object.getOwnPropertyNames(target), name);

      return target[name];

Which, when in use, would yield the following:

We made a custom demo for .
No really. Click here to check it out.

Math = autoCorrect(Math);
console.log(Math.PI); // 3.141592653589793
console.log(Math.PIE); // 3.141592653589793
console.log(Math.PIEE); // 3.141592653589793

Get traps also override the subscript operator because the member and subscript operators use this trap, meaning the following is equivalent to the above example:

Math = autoCorrect(Math);
console.log(Math["PI"]); // 3.141592653589793
console.log(Math["PIE"]); // 3.141592653589793
console.log(Math["PIEE"]); // 3.141592653589793

Strictly typed objects

A slightly more useful variation of the previous use case would be to disallow unknown properties to be used and instead throw an error pointing out the “most likely” candidate.

We’ll re-use the same Levenshtein function as before, but instead of adding a factory function to create the proxy we’ll bake it into the class constructor by returning a proxy to the constructed object instead of the object itself:

class Person {
  constructor() {
    this.age = '';
    return new Proxy(this, {
      get: function(target, name) {
        if (!(name in target)) {
          let alt = getClosestPropertyName(Object.getOwnPropertyNames(target), name);
          throw new ReferenceError(`${name} is not defined, did you mean ${alt}?`);

        return target[name];
      set: function(target, name, value) {
         if (!(name in target)) {
          let alt = getClosestPropertyName(Object.getOwnPropertyNames(target), name);
          throw new ReferenceError(`${name} is not defined, did you mean ${alt}?`);

        target[name] = value;

Which, would yield the following error when a non-existing property is accessed:

p = new Person();
p.age = 30; = "Luke"
p.jedi = true; // ReferenceError: jedi is not defined, did you mean age?


Proxies are incredibly powerful and can be used and abused for a wide array of things, but it’s important to remember that proxies cannot be emulated by a pre-processor and have to be supported by the runtime itself. It’s a rare case for a feature introduced that is not backwards compatible. In most cases, we can achieve the same without proxies although it might involve a bit more boilerplate code.

Another thing to keep in mind is that using proxies isn’t free, there is a non-trivial overhead as there is another level of indirection in play. So in some cases, compile-time metaprogramming might be preferred over doing it at run-time.

Lastly, proxies, while fairly magical, do not necessarily lead to very clean and easily understandable code but they’re worth knowing about as there are certainly a few cases where they may be the best way or even the only way forward.

You come here a lot! We hope you enjoy the LogRocket blog. Could you fill out a survey about what you want us to write about?

    Which of these topics are you most interested in?
    ReactVueAngularNew frameworks
    Do you spend a lot of time reproducing errors in your apps?
    Which, if any, do you think would help you reproduce errors more effectively?
    A solution to see exactly what a user did to trigger an errorProactive monitoring which automatically surfaces issuesHaving a support team triage issues more efficiently
    Thanks! Interested to hear how LogRocket can improve your bug fixing processes? Leave your email:

    : Debug JavaScript errors easier by understanding the context

    Debugging code is always a tedious task. But the more you understand your errors the easier it is to fix them.

    LogRocket allows you to understand these errors in new and unique ways. Our frontend monitoring solution tracks user engagement with your JavaScript frontends to give you the ability to find out exactly what the user did that led to an error.

    LogRocket records console logs, page load times, stacktraces, slow network requests/responses with headers + bodies, browser metadata, and custom logs. Understanding the impact of your JavaScript code will never be easier!

    Casper Beyer Self-proclaimed developer advocate, hate slow software. Grew up with C, work with JavaScript and a fan of Go.

    Leave a Reply