Portrait Dr. Axel Rauschmayer
Dr. Axel Rauschmayer
Homepage | Twitter
Cover of book “Exploring ES6”
Book, exercises, quizzes
(free to read online)
Logo of newsletter “ES.next news”
Newsletter (free)
Cover of book “JavaScript for impatient programmers”
Book (free online)

Setting up constants via proxies

[2018-08-11] dev, javascript, js proxies
(Ad, please don’t block)

In this blog post, a describe a little hack for quickly setting up constants (think enum values, but not wrapped inside a namespace). It is more an educational puzzle than something you should actually use in your code.

A simple version  

TypeScript enums  

As an example, consider the following TypeScript enum (JavaScript itself does not have enums):

enum MyEnum { foo, bar, baz }

assert.equal(MyEnum.foo, 0);
assert.equal(MyEnum.bar, 1);
assert.equal(MyEnum.baz, 2);

I usually prefer to use strings as enum values, because they are easier to debug:

enum MyEnum { foo='foo', bar='bar', baz='baz' }

assert.equal(MyEnum.foo, 'foo');
assert.equal(MyEnum.bar, 'bar');
assert.equal(MyEnum.baz, 'baz');

A pure JavaScript solution  

You can achieve something similar in JavaScript as follows.

const keyProxy = new Proxy({}, {
  get(_target, propKey, _receiver) {
    return propKey;
  }
});
const {foo, bar, baz} = keyProxy;

assert.equal(foo, 'foo');
assert.equal(bar, 'bar');
assert.equal(baz, 'baz');

How does it work? We combine two ingredients to achieve this effect.

First, the proxy is an object where, whatever key you use to read a property, you always get that key as a value:

assert.equal(keyProxy.hello, 'hello');
assert.equal(keyProxy.world, 'world');

Second, using property value shorthands during destructuring lets us specify both a property key and a variable name at the same time. That is, the following two declarations are equivalent.

const {foo, bar, baz} = keyProxy;
const {foo: foo, bar: bar, baz: baz} = keyProxy;

Symbols as values for the constants  

If you use symbols as values for the constants, you get more type safety. The only line of the proxy that changes is line A.

const keyProxy = new Proxy({}, {
  get(_target, propKey, _receiver) {
    return Symbol(propKey); // (A)
  }
});
const {foo, bar, baz} = keyProxy;

assert.equal(typeof foo, 'symbol');
assert.equal(String(foo), 'Symbol(foo)');

Further reading