Basics of Monkey Patching

By  on  

As one of the MooTools team and someone who worked with the Dojo Toolkit for years, I quickly learned one lesson:  you never modify the source of a library when using it on a given web app.  Doing so makes upgrades of the library a nightmare and general maintenance impossible.  So what do you do while you wait for the library creators to fix their bug?  You monkey patch.

So what is monkey patching?  It's the process of replacing methods with updated, "fixing" methods for the original.  In this example we'll presume we have an object with a function called setTransform.  And what's wrong with this example function?  It sets the style of the CSS transform property but doesn't set the vendor-prefixed style required by a few browsers.  In this example we'll fix that problem.

The first step in monkey patching is keeping a reference to the original object (usually a function):

var oldSetTransform = myLib.setTransform; /* function(element, transformValue) { element.transform = transformValue; } */

We keep a reference to the original function because we still want to execute it, we simply want to add to its functionality.

The next step in monkey patching the method is replacing it with a function of the same name on the same object:

myLib.setTransform = function(element, transformValue) {
	/* new function body */
};

With this replacement function added to the new object, we can update it so that it executes its original purpose as well as adds code to do the vendor prefixing:

var oldSetTransform = myLib.setTransform;

myLib.setTransform = function(element, transformValue) {
	element.webkitTransform = transformValue;
	element.mozTransform = transformValue;

	return oldSetTransform.apply(this, arguments);
};

With my example above, the placement of the original function's execution doesn't matter all that much;  as long as the base style and vendor prefixed style are added, things are good.

Oftentimes, however, it's important which order the old method and new functionality are run in.  Let's take another example -- let's say we have a function whose purpose is calculating the tax on an order's total, but the government recently added an additional 1% tax on the total for whatever bullshit they want to waste money on next.  Let's make that happen:

var oldGetTotal = myLib.getTotal;
myLib.getTotal = function() {
	var total = oldGetTotal.apply(this, arguments) + this.getTax();

	return total * 0.01;
};

With the method above, an extra 1% is added on top of the order total plus tax.  But what if you want to give the user a 20% discount?  Then you'd want the discount applied before the tax is applied:

var oldGetTotal = myLib.getTotal;
myLib.getTotal = function() {
	var total = oldGetTotal.apply(this, arguments) * 0.8;

	return total + this.getTax();
};

See how important the position of the original functionality execution can be?

Monkey patching is an essential skill for any advanced JavaScript developer.  You can see how I monkey patched Dojo's menu widget as a real example.  If you're looking to level up your JS skills, it's important you learn the beauty of monkey patching!

Recent Features

  • By
    Create Namespaced Classes with MooTools

    MooTools has always gotten a bit of grief for not inherently using and standardizing namespaced-based JavaScript classes like the Dojo Toolkit does.  Many developers create their classes as globals which is generally frowned up.  I mostly disagree with that stance, but each to their own.  In any event...

  • By
    Regular Expressions for the Rest of Us

    Sooner or later you'll run across a regular expression. With their cryptic syntax, confusing documentation and massive learning curve, most developers settle for copying and pasting them from StackOverflow and hoping they work. But what if you could decode regular expressions and harness their power? In...

Incredible Demos

  • By
    Create a Spinning, Zooming Effect with CSS3

    In case you weren't aware, CSS animations are awesome.  They're smooth, less taxing than JavaScript, and are the future of node animation within browsers.  Dojo's mobile solution, dojox.mobile, uses CSS animations instead of JavaScript to lighten the application's JavaScript footprint.  One of my favorite effects...

  • By
    WebKit Marquee CSS:  Bringin’ Sexy Back

    We all joke about the days of Web yesteryear.  You remember them:  stupid animated GIFs (flames and "coming soon" images, most notably), lame counters, guestbooks, applets, etc.  Another "feature" we thought we had gotten rid of was the marquee.  The marquee was a rudimentary, javascript-like...

Discussion

  1. Rick

    This is a great write-up, but your tax example adds 1% of the already increased value (tax on top of tax.) I think the better solution would be overriding the getTax() function.

    Then (as far as I can tell), your second version of the function discounts the value, but the tax is still calculated from the full value.

    Or did I read it wrong? All of this is irrelevant to the point of the article, but finance stuff should always be solid.

    • I’ve updated my explanation to be a bit clearer. Thanks!

  2. Esen

    This is so beautiful. Thanks.

  3. Good explanation. I have found myself having to do this occasionally over the years too. A helper function like wrap (like in lodash and underscore) can be useful for this too. https://gist.github.com/jfairbank/b356f676a7956ea0ed95

  4. I think the most common Monkey Patching I’ve done in my Web Development career is overriding the broken behavior of Internet Explorer’s document.getElementById() method to overcome its bugs.

    Still sadly one of the worst original implementations of a standard method I’ve ever seen. It’s not like the method name was ambiguous!

  5. weisk

    Isn’t this called the decorator pattern? ;)

  6. Belden

    There’s a small bug in your first myLib.getTotal example. It returns 1% of the total + tax, rather than 101% of the total + tax.

    var oldGetTotal = myLib.getTotal;
    myLib.getTotal = function() {
    	var total = oldGetTotal.apply(this, arguments) + this.getTax();
    
    	return total * 0.01;  // woo, 99% discount!
    };
    

Wrap your code in <pre class="{language}"></pre> tags, link to a GitHub gist, JSFiddle fiddle, or CodePen pen to embed!