on
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 Google CDN:
<!DOCTYPE HTML>
<html>
<head>
<title>A Monkey-patching example</title>
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/dojo/1.4.3/dojo/dojo.xd.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.