Yesterday, I spent some time helping a Product Support Manager debug a headscratcher of a problem. A designer was having trouble getting a JavaScript-powered widget working on a client's blog template, and there was no obvious reason that it shouldn't have worked. Indeed, we use this widget on our own blog template. So what was the issue?
The problem turned out to be caused by a conflict between to widgets on the page. One widget was altering the behavior of a built-in JavaScript object, and the other was using an inappropriate flow control mechanism to iterate over that built-in object. Let's take a closer look at what was happening...
JavaScript developers should be very aware of the array object because it is used frequently to store collections of data. Sometimes, it becomes necessary to locate the index of an item sitting in a given array. Consider the following example:
var hayStack = ["hay", "hay", "needle", "hay"];
The language standard, ECMA-262, does not specify any methods on the Array object that will do this for you. In its implementation of JavaScript 1.6, the Mozilla project introduced an extension, called
This is an incredibly useful method, and the Mozilla project's documentation of this method includes a compatibility note warning the reader that this method may not be implemented on other JavaScript interpreters. As a workaround, they provide a JavaScript implementation of the method's functionality. In essence, it is a monkeypatch.
Problems arise when you have code elsewhere that iterate over the contents of an Array object. The Mozilla Project's online document A Reintroduction to JavaScript provides some guidance on looping over Arrays. The most appropriate and efficient being:
Some JavaScript developers have a bad habit of using the
Note that if someone added new properties to
A few years back, Andrew Dupont wrote a blog post warning of this kind of danger. I've considered it useful enough to use it as a basis for User Interface Engineer candidate interview questions.
Let's now see how this affected the client's template...
The first widget was the Twitter widget developed by Dustin Diaz, which can be found at http://widgets.twimg.com/j/2/widget.js. If you look at the source and sift through the minification, you'll find this definition, which is applied only if the Array object does not have a
The second widget was idTabs, which can be found in unminified form at http://www.sunsean.com/idTabs/jquery.idTabs.js. In the setup code that hides DOM elements, there is this code:
76 //Setup Tabs
...
82 var idList = []; //save possible elements
...
92 for(i in idList) $(idList[i]).hide();
By the time execution hits line 92,
If Array has been monkeypatched with
The bottom line is that the idTabs widget could not function properly on a page where the Twitter widget was also present, provided that the host JavaScript interpreter did not meet the Twitter widget's sniff test for whether the Mozilla Array extensions were defined. In other words, this was broken in Internet Explorer and versions of Firefox prior to version 1.5.
This kind of problem can be difficult to debug because prior to version 8, JavaScript error reporting was awful in Internet Explorer. You'd get a line number with no information about what file was being processed. To get meaningful information, you'd have to install Microsoft's deprecated Script Debugger or use Visual Studio. Moreover, both files are typically used in minified form. I was fortunate in that idTabs provides an unminified version, so I was able to use it in debugging.
The problem turned out to be caused by a conflict between to widgets on the page. One widget was altering the behavior of a built-in JavaScript object, and the other was using an inappropriate flow control mechanism to iterate over that built-in object. Let's take a closer look at what was happening...
JavaScript developers should be very aware of the array object because it is used frequently to store collections of data. Sometimes, it becomes necessary to locate the index of an item sitting in a given array. Consider the following example:
var hayStack = ["hay", "hay", "needle", "hay"];
The language standard, ECMA-262, does not specify any methods on the Array object that will do this for you. In its implementation of JavaScript 1.6, the Mozilla project introduced an extension, called
indexOf(), that returns an integer index indicating the search item's location in the array. In the example above, hayStack.indexOf("needle") would return a value of 2, since arrays index off of zero. If the search item is not present, then a value of -1 is returned.This is an incredibly useful method, and the Mozilla project's documentation of this method includes a compatibility note warning the reader that this method may not be implemented on other JavaScript interpreters. As a workaround, they provide a JavaScript implementation of the method's functionality. In essence, it is a monkeypatch.
Problems arise when you have code elsewhere that iterate over the contents of an Array object. The Mozilla Project's online document A Reintroduction to JavaScript provides some guidance on looping over Arrays. The most appropriate and efficient being:
for (var i = 0, len = a.length; i < len; i++) {
// Do something with a[i]
}
As a side note, it is worth mentioning that care should be exercised in accessing a[i], checking to make sure that it is not of type undefined. This can happen if the array is sparse, meaning not all integer index values from 0 to the length value may be present.Some JavaScript developers have a bad habit of using the
for...in looping construct, which is intended to iterate over the properties of an object. The guide cited above makes the following warning: Note that if someone added new properties to
Array.prototype, they will also be iterated over by [for...in] loop...A few years back, Andrew Dupont wrote a blog post warning of this kind of danger. I've considered it useful enough to use it as a basis for User Interface Engineer candidate interview questions.
Let's now see how this affected the client's template...
The first widget was the Twitter widget developed by Dustin Diaz, which can be found at http://widgets.twimg.com/j/2/widget.js. If you look at the source and sift through the minification, you'll find this definition, which is applied only if the Array object does not have a
forEach() method defined (a sideways way of detecting whether Mozilla's JavaScript 1.6 extensions are implemented):Array.prototype.indexOf = function(B, C){
var C = C || 0;
for(var A = 0; A < this.length; ++A){
if(this[A] === B){
return A;
}
}
return -1;
}
var C = C || 0;
for(var A = 0; A < this.length; ++A){
if(this[A] === B){
return A;
}
}
return -1;
}
The second widget was idTabs, which can be found in unminified form at http://www.sunsean.com/idTabs/jquery.idTabs.js. In the setup code that hides DOM elements, there is this code:
76 //Setup Tabs
...
82 var idList = []; //save possible elements
...
92 for(i in idList) $(idList[i]).hide();
By the time execution hits line 92,
idList is an array of strings that correspond to HTML id attributes. The syntax $(idList[i]) is the jQuery way of doing a DOM getElementById() method call. If Array has been monkeypatched with
indexOf() as described earlier, then you'll get something like idList["indexOf"], which will return a JavaScript Function object rather than another string. Down in the hide() method, the JavaScript interpreter will give you an error because it will try to modify attributes on something that is assumed to be an HTMLElement.The bottom line is that the idTabs widget could not function properly on a page where the Twitter widget was also present, provided that the host JavaScript interpreter did not meet the Twitter widget's sniff test for whether the Mozilla Array extensions were defined. In other words, this was broken in Internet Explorer and versions of Firefox prior to version 1.5.
This kind of problem can be difficult to debug because prior to version 8, JavaScript error reporting was awful in Internet Explorer. You'd get a line number with no information about what file was being processed. To get meaningful information, you'd have to install Microsoft's deprecated Script Debugger or use Visual Studio. Moreover, both files are typically used in minified form. I was fortunate in that idTabs provides an unminified version, so I was able to use it in debugging.








Comments for The Perils of Monkeypatching and Inappropriate Flow Control in JavaScript
Leave a comment