Monkey patching

Recently, David Walsh tweeted that I had schooled him. I received several questions about what I had schooled him in, so I decided to blog about it.

David was trying to change the behavior of a method of a widget on a page, but for all instances of that widget rather than just one instance. What follows is a lesson on monkey patching. I’m sure there are other tutorials out there about it, but this is more for my sanity (and to get David off my back about blogging) than anything else.

Wikipedia defines Monkey patching as “a way to extend or modify the runtime code of dynamic languages (e.g. Smalltalk, JavaScript, Objective-C, Ruby, Perl, Python, Groovy, etc.) without altering the original source code.” For our purposes, we’ll only be looking at JavaScript. Wikipedia also lists four uses for monkey patching:

  • Replace methods/attributes/functions at runtime.
  • Modify/extend behavior of a third-party product without maintaining a private copy of the source code.
  • Apply a patch at runtime to the objects in memory, instead of the source code on disk.
  • Distribute security or behavioral fixes that live alongside the original source code.

In the JavaScript world, monkey patching can be very important if you’re using a third-party library. This is especially true if you are using it from a CDN or are checking it out from a version control system. We’ll use this HTML as our test page and use Dojo from the AOL CDN:

< !DOCTYPE HTML>
<html>
<head>
	<title>A Monkey-patching example</title>
	<script type="text/javascript" src="http://o.aolcdn.com/dojo/1.4.3/dojo/dojo.js"></script>
	<script type="text/javascript">
		dojo.ready(function(){
			dojo.declare("Adder", null, {
				constructor: function(base){
					this._base = base;
				},
 
				add: function(what){
					return this._base + what;
				}
			});
		});
	</script>
</head>
<body>
</body>
</html>

I will assume basic knowledge of JavaScript, object prototype chains, and dojo.declare. The code examples that follow can be pasted into your browser’s JavaScript console (such as Firebug).

Monkey patching object instances

Let’s say you have an Adder instance and you’d like to change the calculation that happens when calling add(). Simply add an add method to the object instance:

var myAdder = new Adder(5);
myAdder.add(5); // returns 10
myAdder.add = function(what){
	return what + 20;
};
myAdder.add(5); // returns 25

That’s all well and good, but how do we perform the original add calculation, yet modify it without rewriting the method completely? Just store the add method to a variable, add an add method to the object instance, and call the stored add method within the new add method. You’ll generally also want to wrap that all in a closure so only the new method can access the old method. Confused yet? Don’t be:

var myAdder2 = new Adder(5);
myAdder2.add(5); // returns 10
(function(){
	var oldAdd = myAdder2.add;
	myAdder2.add = function(what){
		return oldAdd.call(this, what) + 20;
	};
})();
myAdder2.add(5); // returns 30

One note

You might have noticed that I didn’t use the word “overwrite” when talking about monkey patching object instances. This is because object instances look up their properties from their prototype unless explicitly defined on them (like _base). This is what one might call “masking” and is an important concept in JavaScript. When we were monkey patching before, we were actually masking the add method on the Adder prototype (it’s a subtle difference, but important).

Monkey patching an object’s prototype

Great! We can modify individual instances. But let’s say we have 30 Adder instances and we don’t want to modify them all every time. This is where monkey patching the prototype comes in:

var myAdder3 = new Adder(5);
myAdder3.add(5); // returns 10
Adder.prototype.add = function(what){
	return this._base + what + 10;
};
myAdder3.add(5); // returns 20

Here, we’re overwriting (instead of masking) the add method. This applies to instances already created that haven’t had their methods masked (try myAdder.add(5); and myAdder2.add(5);) and future created instances. As we did before, you can also call the originally defined method:

var myAdder4 = new Adder(5);
myAdder4.add(5); // returns 20
(function(){
	var oldAdd = Adder.prototype.add;
	Adder.prototype.add = function(what){
		return oldAdd.call(this, what) + 10;
	};
})();
myAdder4.add(5); // returns 30
myAdder3.add(5); // returns 30

Note how myAdder3 is also affected by this new monkey patch. As you can see, monkey patching is quite simple yet very powerful. Although I used dojo.declare, this can be used to modify any JavaScript object or prototype chain: you can modify a widget’s behavior, add debugging output to a method, or even modify arguments passed to the original function.

Back from hiatus

It’s been a long long time since I’ve seriously blogged and I’m going to get back into it again (at the behest of David Walsh). I’ve got a couple of tutorials and/or tips lined up and I intend to keep up with it. Alright, back to your regularly scheduled programming…