Monday, March 15, 2010

Tuesday, March 2, 2010

A few JavaScript Gotchas

JavaScript was originally written in a hurry as a browser tweaker for Netscape. Then ECMA blindly copied their efforts (warts and all) to form the first standard. Subsequent reluctance to trim the nastiness for fear of breaking legacy support has meant JavaScript has more than its share of  goblins waiting to trip you up.

1. Missed Semicolons

Have you ever revisited your JavaScript code and noticed you missed a semicolon or two from statement endings? Then wondered how come it still worked all this time. JavaScript tries to be forgiving and when it sees a line break without a semicolon will assume you forgot it - and add it for you. As anyone who uses MS Word will tell you, software that goes out of its way to be extra helpful often turns out to be a hindrance.

Take this:

function getActualWidth(innerWidth, specialMargins, extraPadding, lastMinuteBuffering) {
        specialMargins +
        extraPadding +
        lastMinuteBuffering ;

Looks safe enough right? but actually it will always return undefined.
Thats because the JavaScript compiler sees the unterminated return statement and kindly adds the "missing" semicolon for you. Any line of code which could form a standalone statement is subject to this form of misguided generosity (so the lines ending in + are left alone)

Moral: Watch where you put your linebreaks and never rely on JavaScript to clean up after you.

2. Over Coercion 

Coercion - the act of converting one type to another in arithmetic or comparator expressions is sometimes useful. Unfortunately JavaScript drunk a little too much of the coercion kool-aid. Bear in mind = = means compare by coercion, = = =  means compare and don't coerce (types must be the same)  

true == 1; //true! (converts true to bit value 1)
true - false === 1 //true!!!
"2" + 2; //22!
"2" - 2; //0 ;-(
"2" - - 2; //4 :-)
NaN == NaN; //false - this one sort of makes sense since NaN is any non-Number cast to a number
NaN < NaN; //false 
NaN > NaN; //false - whatever!
"" == 0 //true 
undefined == null //false!
"0" == 0 //true! 
"false" = false //false!!!'
'\t\r\n ' == 0 //true!!! - essentially an empty string which coerces to 0 

Are we having fun yet? 

Well  it doesn't end there. JavaScript will also try to coerce anything in an if statement to a boolean. Sometimes this is very useful - but developers need to be clear about what is happening - because it's easy to trip up

if (0) {....} //false
if(-1) {....} //true
if("") {....} //false
if("null") {....} //true

Bear this in mind if you are testing an argument against null pointer. Its easy to accidentally skip 0 values

Moral: == and is(var) can be useful operators for detecting similar values of different types and non boolean equivalents. But you need to understand what is going on under the covers. If you don't, use === and if(var !== null) instead.

3. Auto Globalling

Any time you forget to pre-empt a variable with var it gets defined in the global scope (regardless of closures). This "feature" is known as auto-globalling and its guaranteed to get you at least twice.

Sometimes auto-globalling sneaks in where you least expect it

function doThings() {
    var a = b = 1;
    //a = 1 (local variable)
    //b = 1 (global variable!!!)

This is because javascript always evaluates expressions form right to left. So it starts by assigning 1 to b (not var b - so its global). Then it assigns global b to local a

Its the same as

b = 1;
var a = b;