So I have this syntax highlighter written in JavaScript that uses a stack of parsing modes for its job. This is just a plain Array named modes, and the most common operation in my code is accessing the top element:

modes[modes.length - 1]

… which is kinda ugly, right? So I decided to refactor the stack into a classic linked list to always have a reference to the top element at hand. The refactoring was getting along well and dandy, code becoming neater with every change, until at the last moment I realized that a plain linked list won't cut it…

Consider this JSON snippet:

{ "name": { "lang": "en", "value": "Mary"} }

When working through this code the parser recognizes such modes as arrays, objects, keys, values, etc. So, for example, when the parser reaches the point after "name": the stack of modes looks like this:

But then the parser finds another object and tries to push the corresponding parsing mode on top of the stack by pointing its parent link to the old top. The problem is that modes are singletons and this same mode already exists in the stack and replacing its parent link breaks the whole structure:

One solution to this would be putting on the stack copies of modes but this didn't seem beautiful and might have also proved to be slow. So I decided to wrap modes into individual proxies that would have their own parent links and delegate everything else to real mode objects. This way each proxy can have their own place on the stack but share the actual implementation with others:

The most obvious (to me, anyway) way to implement this in JavaScript is to use prototypes. After all, though it's often called "inheritance", it's actually just delegation: any property that is not found on an object itself is then looked for in its prototype that can be any other object. So this looks simple then:

  1. Create a proxy object with a link to its parent.
  2. Assign a mode object as a prototype for it.

My first take at implementing this was:

top = new function(){this.parent = top}();
top.constructor.prototype = mode;

This didn't work because changing the prototype property of an object's constructor apparently doesn't affect already created objects: they keep their own links to prototypes. So instead of creating and instantiating a constructor in one swoop I had first to create it, then set up a prototype, and then instantiate:

var wrapper = function(){this.parent = top};
wrapper.prototype = mode;
top = new wrapper();

This worked fabulous! But at this point I realized that these measly three lines would most probably fail to make any sense to anyone who would try to understand their purpose. So, however I might like my own clever skills at working around JavaScript's dark corners, I decided not to push that in the main code

Instead, I just blogged it :-)

Comments: 5

  1. yuppy99.ya.ru

    This is interesting hack. But, haven't you considered use of explicit references from linked list to modes instead of implicit ones?

    top = {parent: top, mode: mode};
    
  2. Ivan Sagalaev

    I have, shortly :-). I didn't like the idea of writing top.mode.something instead of just top.something all over the code. Automatic delegation was exactly what I needed, after all.

    BTW, @arik0n on Twitter suggested what seems to be the correct way to create those wrappers, using Object.create that explicitly accepts a prototype for the object, so those three lines become just one:

    top = Object.create(mode, {parent: {value: top}});
    

    However, in order to use it I will have to formally drop support for IE8- (which sorta kinda expected to work for now).

  3. skyboy

    So you have already noted two solutions: make helper function(something like extend()) or implement workaround for old IE versions. And i did not get the point why you think that this way is not acceptable. Any of code flows could be "too complex" for someone. May be it is enough to make good comment in right place. What o you think?

  4. g0rr

    Prototypal Inheritance (or delegation if you wish) is not a dark corner of JavaScript. This is a way to implement classless code reuse. This pattern is described in Stoyan Stefanov's book "JavaScript Patterns":

    function object(o){
        function F() {}
        F.prototype = o;
        return new F();
    }
    
    var mode = { "name": { "lang": "en", "value": "Mary"} };
    
    var top = object(mode);
    top.parent = top;
    
  5. Ivan Sagalaev

    The "dark corner" was that assigning a prototype to the object's constructor doesn't affect already instantiated objects. I was expecting them to refer only the constructor and not keep a separate direct link to the prototype.

Add comment