30 Days of Tech: Day 4 - monkey_patch.js

Yesterday I posted a fix for prototype.js that ensures that the Ajax active request count remains correct even if an application provided hook causes an exception. In the fix I showed a snippet of code from respondToReadyStateChange function of prototype.js along with the snippet’s replacement that corrects the problem. The easiest way to get this change applied in your application would be to simply change prototype.js in place and forget about, but is this really a good idea?

What if you want to upgrade prototype? You could accidentally wipe out this fix, which you may or may not have written a test for (depending on how diligently you test your monkey patching). Even if you remember that you have changes you’ve made to prototype, how do you track them down so you can check whether they are still needed or if they are compatible with the new version? You could use comments around each change, but these comments can quickly become out of date and you’re likely to forget to comment at least one change somewhere.

Fortunately, there is a better answer. Monkey patching in Javascript can be done in a fashion quite similar to Ruby. Since objects in Javascript are simply hashes of functions and variables, all objects (including prototypes used to create new objects) can be changed at anytime. Using this feature you can pull your monkey patch changes out to a separate file. As long as the monkey patch file is included in the HTML page after the code it is patching, you’ll have no problem changing the functionality of the library. Here’s an example:

in app.html:

  <html>
    <head>
      <script type="text/javascript" src="library.js"></script>
      <script type="text/javascript" src="library_monkeypatches.js"></script>
    </head>
    <body>
      <a href="#" onclick="new UsefulObject().solveMyProblem();">Solve My Problem</a>
    </body>
  </html>

in library.js:

UsefulObject = function() {};
UsefulObject.prototype = {
  solveMyProblem : function() {
    alert("There is no problem.");
  }
};

in library_monkeypatches.js:

UsefulObject.prototype.originalSolveMyProbem = UsefulObject.prototype.solveMyProblem;
UsefulObject.prototype.solveMyProblem = function() {
  alert("Help me with my problem!");
  this.originalSolveMyProbem();
  alert("Phew... I feel better now.");
};

Here library_monkeypatches.js copies the original solveMyProblem function to a new name on the same class, then redefines solveMyProblem with some additional behavior, including a call to the original function. If you’re used to Ruby metaprogramming, here’s what it would like in Ruby:

class UsefulObject # re-opening UsefulObject
  alias_method :original_solve_my_problem, :solve_my_problem
  def solve_my_problem
    puts "Help my with my problem!" 
    original_solve_my_problem
    puts "Phew... I feel better now" 
  end
end

Because Javascript gives us some metaprogramming capabilities similar to Ruby, it’s not surprising that a solution we apply in Ruby (pulling out our monkey patches to a separate file) applies equally well to Javascript. Using this technique makes it completely transparent what changes the developers have added to the library. When you upgrade all you need to do is go to the monkeypatches file and verify that all the patches still make sense and work as expected. Much less painful than hunting through the library code discerning what changes were applied!