dcsimg

A Guide to Responsible Monkey Patching in JavaScript

By Rob Gravelle

WEBINAR:
On-Demand

Application Security Testing: An Integral Part of DevOps


Much of my early development work consisted of bug fixing in production systems. One of the first things that taught me was to never change a method's signature. Instead, I would override it with a slightly different list of arguments and would add the updated functionality either before or after delegating to the existing method. I didn't know it at the time, but what I was doing is called "Monkey Patching". Due to its flexible structure and Prototypal inheritance, JavaScript is the near-ideal candidate for monkey patching. Nonetheless, those who have done so have come to realize that overriding object and method behavior in this way is fraught with gotchas. Does this mean that Monkey Patching is a practice that should be outlawed? Not necessarily; it depends when and how you implement your patches. Today's article will provide the basics on the practice of Monkey Patching in JavaScript and offer a few tips on when and how to go about it.

Monkey Patching and Duck Punching

Monkey Patching is a fancy term to describe decorators. The main piece of the decorator pattern, a decorator is a wrapper function that takes in another function and "decorates" it by overriding/extending its behavior without altering the original source code. The Decorator Pattern and Monkey Patching have yet another name, coined by Ruby hackers who decided that they needed a name that reflected their use of duck typing. That's where you try to establish an object's suitability for some purpose, using the principle, "If it walks like a duck and it quacks like a duck, then it must be a duck." In contrast with traditional typing, where an object's suitability is determined according to its type, in duck typing, an object's suitability is determined by the presence of certain methods and properties. Here's an example:

//type checking
if (typeof obj == "number") {
  //OK to proceed…
}

//duck typing
if (obj.coveredWith == "feathers" && typeof obj.quack == "function") { 
  //OK to proceed…
}

The Ruby hackers concluded that, "if this duck is not giving you the noise that you want, you've got to just punch that duck until it returns what you expect."[1]

I'm not entirely sure how punching a duck would yield the results you want, but, in any case, I will be using the more conventional "Monkey Patching" term from here-on-in.

A Basic Example

One of the most common uses of Monkey Patching is to override a method or function by means of a closure whereby the original implementation is passed to the closure so that it may be referenced by the new implementation. Additional processing may be performed either before, after, or indeed, both before and after a call to the original implementation:

object.method = (function (originalMethod) {
  return function (args) {
    //modify before altering original method
    var result = original(args);
    //alter original method or add new behavior 
    return customMethod(result);
  }
})(object.method);

One drawback with the above pattern is that the original method or function has been completely superseded by the new implementation, so, if you ever needed to call the original one, you're plum outta luck!

In my bug fixing endeavours, which were originally in Java, I almost never replaced existing methods. Instead, I created a new implementation that incorporated my bug fix that referenced the original incarnation. That way, I was sure to never break existing code.

To illustrate, here's a simple JavaScript function that accepts two number and returns their sum:

function add(num1, num2) {
  return num1 + num2;
}
document.writeln( "Original function add(1,2): " + add(1, 2) ); //displays 3

Say that I was now tasked with modifying the function to accept any number of arguments as well as prevent NaN results. I named my new function addMore(). If you look closely inside the for loop, you'll see where my new function still references the existing one:

function addMore() {
  var args = [].slice.call(arguments),
      sum  = 0;
  
  for (var i=0; i<args.length; i+=2) {
    //the || 0 handles NaNs
    var num1 = Number(args[i])   || 0,
        num2 = Number(args[i+1]) || 0;
    sum += add(num1, num2);
	}
  return sum;
}
document.writeln( "Monkey Patched function add2(1, 2, 3, 4, 'r'): 
                + add2(1, 2, 3, 4, 'r') ); //displays 10

It might seem silly to keep the original function implementation in such a trivial example, when you could easily just rewrite the entire function, but when dealing with more complex code or functions that are part of a library, or even the JS language for that matter, the virtues of Monkey Patching quickly become apparent.

You can see the add() and addMore() functions on Codepen.

Updating All Function Invocations at Once

In contrast to the above example, most people use Monkey Patching to update methods and functions in a way that alters their original behavior. This is often done on built-in JavaScript and library methods. For example, suppose that you wanted to update the console.log() method so that, in addition to printing the value of the passed argument, it also printed the date and time at which the method was invoked. Here's some Monkey Patching that achieves that goal:

// Save the original console.log() method
var log = console.log;
console.log = function() {
   // Invoke the original method with an additional parameter
   log.apply(console, 
             [(new Date().toString())].concat([].slice.call(arguments))
            );
};

Both the original and Monkey Patched console.log() methods are on display on Codepen.

log("This is the original console.log().", "More text…");
console.log("This is the Monkey Patched console.log()!", "More text…");

//Outputs:
This is the original console.log(). More text…
Tue Apr 10 2018 10:20:31 GMT-0400 (Eastern Daylight Time) This is the Monkey Patched console.log()! More text…

By storing the original implementation in a variable, you can still access it if you need to.

Conclusion

Monkey Patching is not a panacea for putting your own spin on JS methods and functions. There are inherent dangers associated with the practice. Especially when used on built-in objects, Monkey patching can interfere with the evolution of the language. Likewise, when Monkey patching library code, you can never be sure of the exact and full behavior of the methods you're patching. There's always a chance that your version may conflict with other implementations, causing issues in your app or site.

Before attempting to Monkey Patch a library, take a look at the documentation to see whether or not they offer extension points for overriding the default behavior.



Rob Gravelle

Rob Gravelle resides in Ottawa, Canada. His design company has built web applications for numerous businesses and government agencies. Email him.

Rob's alter-ego, "Blackjacques", is an accomplished guitar player, who has released several CDs and cover songs. His band, Ivory Knight, was rated as one of Canada's top hard rock and metal groups by Brave Words magazine (issue #92).



Make a Comment

Loading Comments...

  • Web Development Newsletter Signup

    Invalid email
    You have successfuly registered to our newsletter.

    By submitting your information, you agree that htmlgoodies.com may send you HTMLGOODIES offers via email, phone and text message, as well as email offers about other products and services that HTMLGOODIES believes may be of interest to you. HTMLGOODIES will process your information in accordance with the Quinstreet Privacy Policy.

  •  
  •  
  •  
Thanks for your registration, follow us on our social networks to keep up-to-date