About Fitzblog

Greetings!  I'm Pat Fitzsimmons, a software entrepreneur in Cambridge, MA.  I work for HubSpot, a startup building B2B Marketing Software.  My roles include writing code, designing the product, and plotting strategy.  More about me ...

Developers wanted

HubSpot is hiring developers.

Get Posts by Email

Your email:

My Projects

HubSpot has everything you need to market on the web: web site & blog tools, analytics, seo tools, and marketing campaign tracking.  It's the software used to build this site.  Check it out.

WebSiteGrader - You just spent big money on your new web site. How's it working out for you?  Grade your site.

Current Articles | RSS Feed RSS Feed

Nine Javascript Gotchas

Posted by Patrick Fitzsimmons on Wed, Aug 15, 2007 @ 11:03 AM
Digg digg it | Reddit reddit | del.icio.us del.icio.us | StumbleUpon StumbleUpon 


1) Comma Caused Coruption




<script>
  var theObj = {
        city : "Boston",
        state : "MA",
  }
</script>

Notice the comma after "MA"?  It will be the source of many woes.  Firefox will pay it no heed, but it will create a syntax error in IE.  Worst of all, IE will not tell you where the actual bug is.  The only soution is to scan through your entire 2,500 line javascript file trying to find that extra comma.



2)  "this" can change which object it's pointing at



Take a look at this code sample.

<input type="button" value="Gotcha!" id="MyButton" >
<script>
var MyObject = function () {
    this.alertMessage = "Javascript rules";
    this.ClickHandler = function() {
          alert(this.alertMessage );
      }
}();
document.getElementById("theText").onclick =  MyObject.ClickHandler
</script>


The function MyObject.ClickHandler will actually give a different alert, depending on how it's being called.

If you call MyObject.OnClick(); you will get a popup saying "Javascript rules". 

However, if you click on the button "MyButton", the popup will say "undefined"

When you assign MyObject.OnClick to the even handler, the special variable "this" now referes to the button, not to MyObject.

There are several ways to refer to MyObject.  My favorite is to introduce the "self" variable as a replacement for "this":

<input type="button" value="Gotcha!" id="theText" >
<script>
var MyObject = function () {
    var self = this;
    this.alertMessage = "Javascript rules";
    this.OnClick = function() {
          alert(self.value);
      }
}();
document.getElementById("theText").onclick =  MyObject.OnClick
</script>

Now the alert will say "Javascript rules" no matter what you call. The variable "self" will always refer to MyObject.

Note there is one more gotcha with the above code.  Do not forget the "var" in "var self" or IE will throw a mysterious exception.


3) Identity Theft



Never name a variable the same as an HTML ID:

<input type="button" id="TheButton">

<script>
    TheButton = get("TheButton");
</script>

This will work fine in Firefox but cause and object undefined error in InternetExplorer


4)  String replace only replaces the first occurrence



You might have a code to turn a title into a URL slug:

<script>
    var fileName = "This is a title".replace(" ","_");
</script>

To your chagrin, fileName is actually equal to:
    "This_is a title"

Unlike replace in other languages such as C# or Python, only the first occurence is replaced.  That's because the first argument to replace is actually a regular expression.

To replace all occurences, you need to set the global modifier. Use:

    var fileName = "This is a title".replace(/ /g,"_");

 


5)  MouseOut sometimes means MouseIn



When you have nested div's, the onmouseout event will fire for an outer box when you move inside the inner box.  For context menu's or hover overs, this is not the desired behavior.  My solution is to test for the mouse's location, and only take action if the mouse is actually positioned outside the outer box.

6)  ParseInt scoffs at your base ten numbering system



ParseInt is really nice, because it works with strings that are not pure digits.  I always find myself doing the following:
var height = parseInt("200px")

and get the height.

However, the default call to parseInt has a problem.

Guess what the value of monthInt will be:
  month = "09"
  var monthInt = parseInt(month )

If you guessed 9, then gotcha!  The answer is 0.

When the string begins with a zero, parseInt interprets the value in base 8.  To fix this problem, do the following:

var monthInt = parseInt(month , 10);

Now monthInt will be equal to 9.  The second argument forces parseInt to use a base ten numbering system.




7)  for loops over the kitchen sink



I once had an array as such:
var arr = [5,10,15]
var total = 1;
I iterated over the array:
for ( var x in arr) {
    total = total * arr[x];

}

This piece of code worked fine, until one day, I was getting error.  The error said, "Cannot object  by a number.  This flummuxed me since there were no strings in the array.  But, lo, when I iterated over the array and logged each value, there was an indeed a function object called "find". 

The cause was a javascript library that we had recently installed.  This library added a "find" method to the javascript array object.  Nice to have, but I the "for" loop in javascript will iterate it over all object attributes, including functions. 

But fortunately, the object was not included in the "length" attribute.  Thus to fix the problem, I used another kind of for loop:

for ( var x = 0; x < arr.length; x++) {
    total = total * arr[x];

}

That worked perfectly.



8)  Event handlers Pitfalls


Never set event handlers like the following:

window.onclick = MyOnClickMethod

1)  This will overwrite existing events.  It opens up the possibility of overwriting by some other javascript
2)  This can introduce memory leaks in Internet Explorer in certain circumstances. 

Instead, use a library that abstracts around the event handler, like YUI:

YAHOO.util.Event.addListener(window, "click", MyOnClickMethod);


9)  Focus Pocus



Often when I want to add inline editing to my app, I create a text field and then focus on it:

var newInput = document.createElement("input");
document.body.appendChild("newInput");
newInput.focus();
newInput.select();

However, the above code will create an error in IE.  The reason the even though you have added the element, it is not really available yet.  Fortunately, a split second delay is all we need:

var newInput = document.createElement("input");
newInput.id = "TheNewInput";
document.body.appendChild("newInput");

setTimeout("document.getElementById('TheNewInput').focus(); document.getElementById('TheNewInput').select();", 10);



COMMENTS

Thanks for this great post! For the first one, note that if you use an editor like Komodo Edit, it will show you trailing commas by underlining in green (for warning vs red for error). There's a button to jump to the next Javascript error/warning so you won't miss any. It tells you when you use a reserved word as variable name as well (like "enum" - which happen to work sometimes).

posted @ Wednesday, August 15, 2007 12:04 PM by Erwan


Great list! The comma induced problem is my ultimate enemy. *bookmarked*

posted @ Wednesday, August 15, 2007 12:09 PM by Patrick Burt


What's going on in #2 is that functions are first-class objects, so when you set the click handler you're using the function object separate from the object it was created on. Write document.getElementById("theText").onclick = function() { MyObject.ClickHandler(); } if you want to use the function in the context of the object. I highly recommend Douglas Crockford's writings on Javascript at http://javascript.crockford.com/ for a proper introduction to JS.

posted @ Wednesday, August 15, 2007 12:49 PM by Peter Harkins


In #9, you've got a syntax typo. Instead of: document.body.appendChild("newInput"); You want: document.body.appendChild(newInput);

posted @ Friday, August 17, 2007 1:00 AM by Paul Irish


At number 9, why not use a window.onload event? (or $( document ).ready( function(){} ) in jQuery).

posted @ Friday, August 17, 2007 4:21 AM by Adrian


Great list. Thank you for drawing attention to these. Funny how more than one of the "Javascript" gotchas you mention include some version of "but it doesn't work in IE". I agree that JS is full of vexing oddities, but IE's ever-poor implementation sure doesn't help. I find developing in JS is often a two step process: 1) develop according to standards and documentation, 2) debug application's problems in IE. BTW, you can use the bind method of functions to assign the value of this within that function. I find that more elegant that defining another variable to point to a value of 'this'.

posted @ Friday, August 17, 2007 9:56 AM by Michael Harrison


Great list, but I've got a couple of remarks. Firstly, as Adrian already pointed out, number 9 would be a lot more elegant with an onload, or better yet, domready event. I don't know about YUI, but like jQuery, MooTools makes the latter really easy. Secondly, number 7 is a no-brainer. The |for (k in a)| construct is meant to iterate over an associative array's keys (or an object's attributes), while the standard |for (;;)| loop works with arrays like in any other language. I can see how some people would get confused, but it's basic stuff. Other than that, this will probably be very helpful to a lot of people.

posted @ Friday, August 17, 2007 11:30 AM by ct^


It took me about 2 hours to figure out the same solution 2 weeks ago you mentioned in #9. IE is a pain in the ass.

posted @ Saturday, August 18, 2007 6:05 PM by Dzsekson


A useful list. :) However, point #9 is subject to race conditions. As your first snippet shows, appendChild() is apparently not synchronous in MSIE. That means that your second example may also fail, depending on any number of conditions which can cause a delay in the JS engine. Threading is always a difficult issue, and in JS, which has absolutely no support for proper thread handling, depending on this type of timing can be fatal to an application. A better approach would be to use a library which allows you to add an "onReady" handler to arbitrary DOM nodes. This allows you to add the focus handler inside the onReady handler for the new DOM element. The jQuery library supports such a feature, if i'm not mistaken.

posted @ Thursday, September 13, 2007 10:51 AM by Stephan Beal


var newInput = document.createElement("input"); newInput.id = "TheNewInput"; document.body.appendChild("newInput"); setTimeout("document.getElementById('TheNewInput').focus(); document.getElementById('TheNewInput').select();", 10); please fix the incorrect syntax Thanks

posted @ Wednesday, November 07, 2007 11:17 AM by a robinson


3, 7 and 9 are JScript gotchas, not JavaScript gotchas.
Checking mouse position in mouseover/mouseout is wrong. You should be checking event.target (event.srcElement in IE). Read about event bubbling.
Also, don't use implied eval in setTimeout. Instead of string and ugly re-fetching of object, pass it a function:
setTimeout(function(){variables work here!},100)

posted @ Thursday, February 14, 2008 9:48 AM by kl


Re: 2.
This is a bad practice. Don't create needless pointers. Instead, create a binding to bring the right "this" into the function:
<code><pre>
var MyObject = function() {
this. sent to the function. (I also noticed your empty parentheses at the end of the function. Totally unnecessary.)

posted @ Thursday, February 14, 2008 10:12 AM by Stephen


Here's another one: boolean / int / string comparison
http://www.robertames.com/blog.cgi/entries/how-does-javascript-suck.html
--Robert

posted @ Thursday, February 14, 2008 10:39 AM by Robert


For #8, why not just use the standard DOM APIs instead of resorting to a third party library?
window.addEventListener(SomeClickHandler, "click", false);

posted @ Thursday, February 14, 2008 12:01 PM by Tony


@Robert: Lamest "gotcha" ever; what do you think JavaScript should do?! Guess what you mean re: string interpolation/conversion?

posted @ Thursday, February 14, 2008 12:51 PM by Dave


Wow, thanks a ton. Those are some great tips. I can't imagine how much trouble you had to go through to learn all those.

posted @ Thursday, February 14, 2008 1:26 PM by Kevin Day


Number 1 is my nemesis. I had just made an XHR call that worked fine in Firefox but refused to work in IE.
Two hours later I found the missing comma
Two hours and 10 minutes later I replaced my own JSON-output with one generated by a library function (json_encode in PHP)
The lesson is: Do not ever generate your own JSON or XML. It will bite you in the ass later

posted @ Thursday, February 14, 2008 2:47 PM by OhNo


@Dave
Boolean( "" + true ) ? "yes" : "no"
Boolean( "" + false ) ? "yes" : "no"
Boolean( "" + 1 ) ? "yes" : "no"
Boolean( "" + 0 ) ? "yes" : "no"
Number( "" + true ) ? "yes" : "no"
Number( "" + false ) ? "yes" : "no"
Number( "" + 1 ) ? "yes" : "no"
Number( "" + 0 ) ? "yes" : "no"
Round-tripping boolean data in Javascript to and from strings requires custom code. And the boolean-ity behaves differently from Number(). That is the gotcha.

posted @ Thursday, February 14, 2008 4:10 PM by Robert Ames


About #7.
When you have to iterate a Object instance you have no choice then using a *for in* statement. So, what to do in this case? Simple! Let's say you have this code:
=--
Object.prototype.oops = function() { /* whatever */ };
var obj = {a: 1, b: 2};
=--
Now what?
Just code like below:
=--
var op = Object.prototype;
for(var e in obj) {
if(!(e in op) || obj[e] != op[e]) t match with any Object methods or proprieties.

posted @ Thursday, February 14, 2008 6:51 PM by Leandro Nascimento Camargo


About #7.
When you have to iterate a Object instance you have no choice then using a *for in* statement. So, what to do in this case? Simple! Let's say you have this code:
=--
Object.prototype.oops = function() { /* whatever */ };
var obj = {a: 1, b: 2};
=--
Now what?
Just code like below:
=--
var op = Object.prototype;
for(var e in obj) {
if(!(e in op) || obj[e] != op[e]) s Object method/propriety.

posted @ Thursday, February 14, 2008 6:56 PM by Leandro Nascimento Camargo


Thanks for the article Patrick..

posted @ Friday, February 15, 2008 12:02 AM by shoban


@Stephen: Re: 2.
Using parentheses is neccessary, because it sets the "OnClick" property of the function. Functions are not called just by declaring them ;)
I would add one more gotcha - use the "hasOwnProperty" function to check if a property is a native property or an inherited property - things can get messy when you use frameworks that modify native javascript objects.
for (var prop in obj) if (obj.hasOwnProperty(prop)) {
...
}
PS
JSLint is your friend :)

posted @ Friday, February 15, 2008 4:31 AM by Radek Litman


Another gotcha: Javascript closures seem to capture pointers, not values. Try this:
for (var i = 0; i < 4; i++) {
setTimeout(function() { ll see all 4 .
I avoided the problem by using recursion instead of a loop.

posted @ Thursday, February 21, 2008 5:23 PM by Dan Webb


Post Comment
Name
 *
Email
 *
Website (optional)
Comment
 *

Allowed tags: <a> link, <b> bold, <i> italics

Receive email when someone replies.