DEV Community

Samuel Rouse
Samuel Rouse

Posted on

JavaScript Quick Bits: Computed Methods

You probably know about computed keys in objects, but did you realize you can use computed keys with the method shorthand? You probably don't ever want to do this, but you can.

const methodName = 'myMethod';
const computedKey = 'computed';

const myObj = {
  // Computed Property
  [computedKey]: 'It worked!',

  // 🤔 Is this a good idea? Probably not, no.
  // Computed property + Method shorthand
  [methodName]() {
    return this.computed;
  },
};

myObj.myMethod();
// 'It worked!'
Enter fullscreen mode Exit fullscreen mode

History Lesson

In the earliest versions of JavaScript, functions were all defined in the global scope. Even back then you could use brackets to get or set computed values, but most of the capabilities we think of weren't available.

ECMAScript 3

ECMAScript 3 brought us function expressions and object methods. You could use bracket notation to set properties or methods.

// Old JavaScript.
var computedKey = 'computed';

var myObj = {
  // Key and function separately.
  myMethod: function () {
    return this.computed;
  },
};

myObj[computedKey] = 'It worked!';

myObj.myMethod();
// 'It worked!'
Enter fullscreen mode Exit fullscreen mode

It was possible to have dynamic method names if you wanted but they had to be defined after the object was created.

myObj = {};
myObj[methodName] = function() {
  return this.computed;
};
Enter fullscreen mode Exit fullscreen mode

ECMAScript 2015

ECMAScript 2015 introduced both object method shorthand and computed properties.

// Exciting new JavaScript!
const computedKey = 'computed';

const myObj = {
  // Method shorthand
  myMethod() {
    return this.computed;
  },
  // Computed Property
  [computedKey]: 'It worked!',
};

myObj.myMethod();
// 'It worked!'
Enter fullscreen mode Exit fullscreen mode

Even though the MDN article doesn't specifically mention it, you can mix method shorthand with computed property names, as shown at the beginning of the article.

Problems

There might be some edge cases where it makes sense to do this, but in general we should avoid this technique. It makes it very difficult to locate methods when trying to understand the code, and reduces the effectiveness of code editor support like IntelliSense and type information.

Alternatives

Hashes or proxies can be good alternatives to computed method names. Take a look at some of the ways we could make this work and let me know which ones you think would work best for you!

Common Code

const methodName = 'myMethod';
const computedKey = 'computed';

const myObj = {
  getComputed() {
    return this.computed;
  },
  [computedKey]: 'It worked!',
};
Enter fullscreen mode Exit fullscreen mode

Plain Mapping

A simple map of strings to match up method names requires very little setup but makes it a little harder to call the methods.

const methodMap = {
  [methodName]: 'getComputed',
};

myObj[methodMap.myMethod]();
// 'It worked!';
Enter fullscreen mode Exit fullscreen mode

Bound Mapping

Using an object with methods bound to the original object requires more setup but simplifies the code for the consumer.

const methodMapBound = {
  [methodName]: myObj.getComputed.bind(myObj),
};

methodMapBound.myMethod();
// 'It worked!'
Enter fullscreen mode Exit fullscreen mode

Basic Proxy

A Proxy object eliminates most of the complication, as you can interact directly with the proxy. This uses a static check in the getter to find our computed property.

const basicProxy = new Proxy(myObj, {
  get(target, prop, receiver) {
    if (prop === methodName) {
      return myObj.getComputed;
    }
    return Reflect.get(...arguments);
  },
});

basicProxy.myMethod();
// 'It worked!'
Enter fullscreen mode Exit fullscreen mode

Proxy Plus Map

Using a property name map a in the Plain Mapping example, we can support any number of method mappings. If we are willing to accept a little risk of infinite loops, we can actually support multiple indirection!

const methodMap = {
  [methodName]: 'getComputed',
  'thisIsWild': methodName,
};

const methodProxy = new Proxy(myObj, {
  get(target, prop, receiver) {
    if (methodMap[prop]) {
      // Using receiver allows multiple indirection
      return receiver[methodMap[prop]];
    }
    return Reflect.get(...arguments);
  },
});

methodProxy.myMethod();
// 'It worked!'

methodProxy.thisIsWild();
// 'It worked!'
Enter fullscreen mode Exit fullscreen mode

Conclusion

I hope you enjoyed this brief wander down some unexpected and (hopefully) unused capabilities of JavaScript!

Top comments (0)