Decorators and class names in JavaScript

I was working on a project last week were I was trying to upgrade a JavaScript class using the decorator pattern:

function Deck() { /* some constructor */ }
var DeckWithState = State.decorate(Deck);
var spades = new DeckWithState();
spades.set('suit', Deck.SPADES);

The gist here being that you have some base object of your own definition (Deck) to which you want to add functionality (State). You might not want to use a mixin, as that tends to work on individual objects, and you might have any number of them. You might not want to use some library’s extend, as there may be more complex decoration logic than a simple “copy all these things” solution could perform. You need an actual decorator, which returns a new constructor that wraps your base class.

You try something like this:

function decorateInstance(options) { /* some complex logic here */ }
State.decorate = function (Klass, stateOptions) {
  function StateWrapper () {
    Klass.apply(this, arguments);
    decorateInstance(this, stateOptions);
  StateWrapper.prototype = Klass.prototype;
  return StateWrapper;

This is fine, and it works as expected. State.decorate returns a StateWrapper constructor. This constructor passes any arguments straight on to the original Klass (Deck) constructor, and sets up the correct prototype chain, so the wrapped/decorated class can be used just like the original.

var DeckWithState = State.decorate(Deck);
var spades = new DeckWithState();
console.log(spades instanceof Deck); // true

But there’s one extra thing that bothers me: debugging.

console.log(spades); // StateWrapper {...}
console.log(spades.__proto__); // Deck {...}

That the object shows up as a StateWrapper is a mis-feature. I could apply State decoration to any number of classes in my app. With this decorator, all of them would show up in the debugger as StateWrapper instances, no matter what their base class is. Sure, you can drill into the prototype chain as I have done, but debuggability is about seeing things quickly at a glance.

Well, we could dynamically define our wrapper using (gasp!) eval:

State.decorate = function (Klass, stateOptions) {
  var className = + "WithState";
  var Wrapper = null;
  eval("Wrapper = function " + className + "() {" +
    "Klass.apply(this, arguments);" +
    "decorateInstance(this, stateOptions);" +
  Wrapper.prototype = Klass.prototype;
  return Wrapper;

It’s ugly, but it works:

console.log(new (State.decorate(Deck))()); // DeckWithState {...}


But can we do it without the evil eval? Yes we can, if you remember that you can use Function in a similar way:

State.decorate = function (Klass, stateOptions) {
  var className = + "WithState";
  var decoratorFactory = new Function("Klass", "className", "instanceDecorator", "decoratorOptions",
    "function " + className + "() {" +
    "  Klass.apply(this, arguments);" +
    "  instanceDecorator(this, decoratorOptions);" +
    "}" +
    className + ".prototype = Klass.prototype;" +
    "return " + className + ";"
  return decoratorFactory(Klass, className, decorateInstance, stateOptions);

This is effectively the same thing, but without all of the linter whining about eval. We can’t just use Function to create a new constructor, as functions created that way are disconnected from any closed scope — we would lose access to Klass and the other things we need. Instead, we use Function as a factory, passing in only what we need for our decorator constructor. It works as expected:

console.log(new (State.decorate(Deck))()); // DeckWithState {...}

It’s something of a long way to go just to get prettier instance class names in the debugger. But if we figure that you’re working with some library where you’d only have to pay this cost once, it may well be a cheap and easy way to save a lot of time down the road.

/* State.js */
Decorator = require('./decorator');
function State() { /* some constructor */ }
// this would set up State.decorate(Klass, decoratorOptions)
Decorator.mixin(State, {
  decorateInstance: function(options) { /* some logic */ }

Once you’ve got your Decorator module set up, upgrading any of your other classes to support decoration becomes trivial.

It turns out that you can do something similar with ES6. A constructor has a configurable name. It’s read-only by default, but because it’s configurable you could set it to read-write, change it, then set it back to read-only. But, of course, you need a browser that supports this. Transpiling from ES6 source to something IE7-compatible isn’t going to cut it, as there’s no non-ES6 equivalent.

By Rick Osborne

I am a web geek who has been doing this sort of thing entirely too long. I rant, I muse, I whine. That is, I am not at all atypical for my breed.