Method Rewriting: Running With A Lit Stick Of Dynamite


I had a problem I wanted to solve. I thought to myself, “I know! I’ll use function rewriting!” and it was good… I solved the problem. But I also introduced some other problems regarding method references and event handlers.

Method Rewriting

If you’re not familiar with method rewriting in JavaScript, it’s basically just replacing a method at runtime, from within the method itself.

In this example, the “foo” method contains code to replace the “foo” method with a new method implementation. When the “foo” method is run the first time, it outputs “bar”. Then it replaces the “foo” method with the new implementation. Subsequent execution of this method will output “quux” instead of “bar” because the method has been rewritten.

And BOOM Goes The Dynamite

The problem I ran into has to do with the way we set up event handlers in JavaScript, by passing function references around. Consider the following Backbone.View implementation:

When the view is instantiated, a “change” event from the view’s model is handled. When the model changes, it calls the “render” function. This ensures the view is always up to date with the latest data from the model. It’s not a very good solution from a performance perspective, but it solves the general problem.

Now let’s introduce a more complicated scenario where the first time the view renders, it should do a little bit of extra work. Subsequent execution of the render method should not do that extra work. I’m not going to get in to detail about what the extra work might be in a real application, but I can illustrate the point using some simple console.log calls.

When you run the above code, the first call to the render method logs the “extra work” message. Calling the render method a second time, directly, does not produce this message. That’s good. It means our function re-writing worked. However, changing any of the data on the model will produce the “extra work” message, which is not what we want. Worse still, it will replace the “render” method on the view instance again, and again, and again, every time the change event fires.

Lighting The Dynamite

The problem is the way we hand off a reference to the function for the event handler. When we call `this.model.on(“change”, this.render, this);`, we are handing a method pointer to the “on” function – a callback that points directly to the current implementation of the “render” function. This is not a reference that gets evaluated every time the change method fires. It is evaluated immediately when the event handler is set up, and a direct reference to the render function itself (not the object that holds on to the render function) is passed through as the event handler.

When the render function is called later on, the render function itself replaces the view’s “render” function with a new function. It re-writes itself… except this is a bit of a misnomer. The function it not actually re-writing itself. What it is doing, really, is replacing the view’s reference to itself with a reference to another function. It’s the equivalent of this:

In this code, it is very clear that the “render” method is being replaced. The original function is not being “re-written”, but only replaced. Method re-writing does exactly this, but does it from within the function that is being replaced.

Now, getting back to the event handler… when the “render” function replaces itself, it only replaces the function on the view instance. It doesn’t have a reference to the “on” method and it doesn’t know how to replace that event handler with the new function reference. Therefore, the “on” event handler still references the original render function, even after the render function has replaced itself on the view.

The result is that the “change” event fired from the model will cause the original render function to be executed. Since the original render function contains the method re-writing code, it will replace the view’s render method again. Every time the “change” event fires from the model, this will happen. If the “change” event fires 3 times, the “render” function on the view will be replaced 3 times.

Powerful But Dangerous

Lesson learned: method rewriting is like running with a lit stick of dynamite. It’s a powerful tool that serves a purpose – but the scope and references to the functions that are being re-written need to be controlled very tightly, or it will blow up in your face.

Some Notes On Screencasting