Modify Existing JavaScript Functions
February 7, 2007
I often find myself needing to tweak existing functions in JavaScript. There are a few cases of standardized native JavaScript object methods that have very useful features that some pesky browser forgot to implement. The method exists, but it doesn’t conform to the standard. How can I possibly fix that broken browser?
Other times I’m working with a useful JavaScript library and need to use one of its functions, but realize that there’s a bug in the code, or a certain edge case it doesn’t handle properly that keeps coming back to bite me. I’m tempted to just add my own hack to the library code and be done with it, but there is a better way.
Because functions are first-class objects in JavaScript, we can assign new function values to existing ones without anyone being the wiser. And thanks to the power of closures, our new function can remember the old one without any other code being able to access the original version. I’ve sometimes seen this called a Proxy Pattern, where our new function acts as a pass-through to the old one without the outside world knowing that the call has been intercepted.
If It Ain’t Broke, Don’t Fix It!
Before you go fixing things, you should make sure they’re broken in the first place. If you plan on fixing a broken browser implementation, then make sure it really is broken before you go to the trouble of changing it (and introduce new bugs to an already working version). The unobtrusive JavaScript philosophy advocates object detection over browser sniffing. This works fine for browsers that don’t implement certain methods like Array.push() or Function.call(), but you need a more thorough test for a broken function.
The solution is to create a simple test of the function that probes the issue you are trying to correct. I got the idea for this technique from Dan Webb’s Code Highlighter script. Older versions of Safari don’t support callback functions for String.replace(). Code Highlighter needs this functionality, so it does a simple test to see if it needs to be fixed:
if ('a'.replace(/a/, function () { return 'b'; }) !== 'b') {
// Fix the String.replace method
}
In browsers that support callback functions for String.replace(), nothing will be changed. Browsers that don’t support the callback function can easily be detected with this simple test.
I was recently working on a script that needed the String.split() method to hold onto the delimiter in the returned array (this is very useful for parsing). If you use parenthesis on the regular expression delimiter, the delimiter text is supposed to be included in the final array. Internet Explorer does not include the text. I wrote a function for fixing this, but first needed to test if it was broken in the first place:
if ('a b'.split(/( )/)[1] !== ' ') {
// Fix the String.split method
}
Testing can extend to library functions as well, unless you know for sure that the version of a library that you are using implements a function in a specific way.
Don’t Throw Away Other People’s Work
Once you’ve determined that a function needs to be modified, you don’t want to lose the existing version of it. Why? Because someone else has probably already done most of the work for you. You just need to filter input to the function, or modify its existing output slightly.
The trick is to use the powerful and often misunderstood closure. Closures allow you to access the original function from the new one without other code having access to the original. There are 2 similar syntaxes for accomplishing this, and you should use whichever suits your style. Dan Webb’s Code Highlighter uses the following syntax:
(function(){
var default_replace = String.prototype.replace;
String.prototype.replace = function(search,replace){
// replace is not function, apply original and return
if(typeof replace != "function"){
return default_replace.apply(this,arguments)
}
// search string is not RegExp, apply callback once on first matched substring
if(!(search instanceof RegExp)){
var idx = str.indexOf(search);
return (
idx == -1 ? str :
default_replace.apply(str,[search,callback(search, idx, str)])
)
}
// Otherwise replace the matched expression
// with the result of the callback function manually
}
})();
This example creates an anonymous function and invokes it immediately. Inside the function body, the old replace() method is assigned to the local variable default_replace. Then the replace() method is redefined with a new function that still has access to the old version. The new replace() method then does a series of checks to see what arguments were passed in. If the replace argument wasn’t even a function, it simply returns the result of the original function implementation. Otherwise, it augments the result of default_replace in a manner consistent with the ECMAScript standard.
Here is an example of an alternate syntax that accomplishes the same thing with the String.split() method:
String.prototype.split = (function (old) {
return function (delimiter, limit) {
var result = old.apply(this, arguments);
// If delimiter was RegExp and contained parenthesis,
// modify the result of the original method to adhere to the standard
};
})(String.prototype.split);
This syntax also creates an anonymous function and invokes it right away. But this version passes in the existing value of the String.split() method as an argument to the anonymous function. This argument is named old, and can be accessed throughout the function. Then the anonymous function returns the new value of the String.split() method, which retains access to the old variable. I personally find this syntax a little more elegant, but it may potentially be more confusing to anyone else reading your code.
An Everyday Example
This technique can be used in a common, everyday JavaScript problem. That problem is the window.onload conundrum. You can never be quite sure who else has attached a handler to this important event, and if you’re not using an Event abstraction layer that can implement the Delegate Pattern of the W3C and Microsoft’s implementations, then you may accidentally overwrite another onload event. When you need a quick and dirty solution, the following can get the job done:
window.onload = (function (old) {
return function () {
if (typeof old == 'function') old();
// Run new code here...
};
})(window.onload);
Learn to Program in JavaScript’s Dialect
JavaScript is an interesting language with some very strange colloquialisms. These sayings can look like strange slang, but are very powerful when you don’t have access or time to worry about a library that abstracts away the unusual syntaxes of JavaScript. I have become a better programmer since I’ve embraced the quirky syntax that is possible yet powerful in JavaScript.
February 8th, 2007 at 11:41 am
2nd example, missing “(” before “function”
October 19th, 2007 at 2:06 am
Do you have any idea if this “fixing” is also possible in general for any JS function on a page? What I want to achieve is that I have a library of functions and I want to protocol certain things for each of these function, so basically for each call of a function, I would like to first call a protocol function, then carry out the original function call and then call a second protocol function. So, rather than having to put the protocol functions into every function, more or less impose them when needed.