When working on a project, we often use libraries that implement methods that aren’t built-in in the programming language in use. These libraries don’t cover all the possibilities, so they might lack one or more crucial features we need. When this happens, we have two choices: the first is to create our own module that implements the methods we need, the second is to add those methods to the library itself or to the built-in objects of the language. The latter approach is known as Monkey patching.

In this article, we’ll look at what Monkey patching is, what pros and cons it has, and also a couple of examples that employ this technique.

What is Monkey patching?

Monkey patching is a technique to add, modify, or suppress the default behavior of a piece of code at runtime without changing its original source code.

It has been extensively used in the past by libraries, such as MooTools, and developers to add methods that were missing in JavaScript. A well-known example is the endsWith() method, which has been introduced in ECMAScript 2015. Before this version, developers had to implement this method themselves. As mentioned before, there are a couple of ways to do that.

One possible approach is to create a Utility object that exposes a endsWith() method as shown below:

var Utility = {
   endsWith: function(string, suffix) {
      return string.indexOf(suffix, string.length - suffix.length) !== -1;
   }
};

With this code in place, the developer would call the method as follows:

var isSuffix = Utility.endsWith('hello world', 'ld');

Another approach is to Monkey patch the built-in String object to expose the method:

String.prototype.endsWith = function(suffix) {
   return this.indexOf(suffix, this.length - suffix.length) !== -1;
};

With this approach, the developer would call the method as follows:

var isSuffix = 'hello world'.endsWith('ld');

At first glance, the second version makes more sense because an endsWith() method logically belongs to a string. However, as we’ll discuss later, this approach can cause several problems.

The endsWith() example demonstrates how to use Monkey patching to add a method to a built-in object, but Monkey patching can be used also to change the behavior of a method.

Using Monkey patching to change a method

Many developers tend to use console.log() when debugging their code. This technique shouldn’t be used by professional developers, as they should adopt breakpoints and watch variables. For the sake of this discussion, let’s pretend that console.log() is the best way to debug code and that you’re using it to inspect the value of a variable at different points in time. Everything is working well, but you realize that it would be nice if in addition to printing the value of the variable, the method printed the date at which the method was called. To achieve this goal, you can employ Monkey patching as follows:

// 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(arguments));
};

In this code, we first save the original console.log() method in a variable named log. This allows us to reuse the original method whenever we want, as we’ll see in a moment. Then, we assign an anonymous function to the property log of the console. In other words, we override the original console.log() method. Inside the anonymous function we invoke the original console.log() method, stored in the log variable, by passing to it all the arguments passed to the anonymous function (through the use of arguments) prepended by the current date and time.

Now that we’ve discussed what Monkey patching is and how to employ it, let’s examine why it’s usually considered a bad practice and when it can be helpful.

The cons of Monkey patching

Monkey patching is often considered a dangerous technique. Not too long ago, the use of Monkey patching has caused the rename of ECMAScript’s 2015 method String.prototype.contains() to String.prototype.includes(). This change was necessary to avoid that websites using MooTools, which adds a contains() method to String.prototype, were broken due to introduction of the ECMAScript version of the method. In particular, the problem was that the contains() method added by MooTools and the contains() method added in ECMAScript 2015 were incompatible. If you want to know more about this topic, you can read the include() method page on MDN.

The lesson to learn here is that Monkey patching, especially when used on a built-in object, can interfere with the evolution of the language. This rule can also be extended to any code that you don’t own, such a library or a framework. Another point to keep in mind when Monkey patching code you don’t own is that you can never be sure the behavior of the methods you patch. In fact, in the same way you have changed a method, another developer in another part of the software might have done the same. So, chances there are that the two versions will conflict causing issues in the software.

When to use Monkey patching

One case where Monkey patching is a good choice is when it’s used to polyfill methods that are already part of the ECMAScript standard and that are not supported by every browser. This is a safe choice because the method has already been standardized, thus you know its signature and its exact behavior. In addition, by using a defensive technique, you can ensure that you add your own version only if the built-in one isn’t available. To understand how you can safely employ Monkey patching, let’s see an example.

As mentioned before, one of the methods introduced in ECMAScript 2015 is String.prototype.includes(). It returns true if one string is contained within another; false otherwise. Its signature is the following:

String.prototype.includes(searchString[, position])

searchString is the string to be searched and position is an optional parameter to specify the position at which to begin searching for searchString. The default value of position is 0.

To support browsers that don’t expose the String.prototype.includes() method, we can employ Monkey patching as follows:

String.prototype.includes = function(searchString, position) {
   'use strict';
   if (typeof position !== 'number') {
      start = 0;
   }

   if (position + searchString.length > this.length) {
      return false;
   } else {
      return this.indexOf(searchString, position) !== -1;
   }
};

If we use the previous snippet as is, we also override the native implementation of String.prototype.includes() in browsers that support the method natively. To avoid this effect, we should only add the function if it doesn’t exist in the browser. This can be achieved in two, equivalent ways.

The first solution is to use an if:

if (!String.prototype.includes) {
   String.prototype.includes = function(searchString, position) {
      // Implementation of the method here...
   };
}

Another approach is to use the || (OR) logical operator:

String.prototype.includes = String.prototype.includes || function(searchString, position) {
   // Implementation of the method here...
};

Conclusions

In this article, I’ve covered what Monkey patching is and how it can be used in JavaScript. As we’ve discussed, in most of the cases Monkey patching is dangerous and can cause issues, therefore you should avoid it. We’ve also seen a case where Monkey patching a JavaScript built-in object is a good choice: polyfills. Using Monkey patching in conjunction with a defensive technique to polyfill a method that is already part of the ECMAScript standard but it isn’t supported by some browsers is a great way to avoid creating branches in your code. It also avoids the risk of conflicting with other parts of your software or future features the language.

Monkey patching in JavaScript
Tagged on: