February 10, 2007

Tricky alternatives to toString() and parseInt()

JavaScript is Loosely typed

JavaScript is dynamically typed, making things extremely flexible. Suppose you have a text field for inputting birth year and want to greet to people born after 1984, simply write

var birthYear = document.getElementById('txtBirthYear').value;
if (birthYear > 1984) {
alert('Greetings!');
}


When JavaScript sees you compare a String with a Number, it automatically converts the String to Number and the perform comparison.



But sometimes the ambiguity of type causes problems. 1 + '1' evaluates to '11' instead of 2. This may cause hard to find bugs. Douglas Crockford categorizes "the overloading of + to mean both addition and concatenation with type coercion" as a design error of JavaScript in his famous article on The World's Most Misunderstood Programming Language.



Still we often need to convert data type between String and Number. To convert variable i to String, simply call i.toString(). To convert s to a Number, we use Number(s). This is nice and clear.



The empty string ('') concatenation trick, the plus sign (+) trick and the minus zero (- 0) trick



But for guys who want to squeeze every byte. There are tricky alternatives.



  • To convert x to String: x + ''
  • To convert x to Number: +x
  • To convert x to Number: x - 0




For examples,

1 + 2 + 3 //produces 6
//while
'1' + 2 + 3 //produces '123'
'1' - 0 + 2 + 3 //produces 6
'1' + '2' //produces '12'
//while
+'1' + +'2' //produces 3

Notice that +x and x-0 doesn't mean parseInt(x) or parseFloat(x), it doesn't do any further parsing.

parseInt('2007 is promising') //produces 2007
//while
+'2007 is promising' //produces NaN
Let's call them the empty string concatenation conversion trick, the plus sign and the minus zero trick. Both of the tricks sacrifice clarity and make code harder to understand.

February 6, 2007

Closure, eval and Function

eval() evaluates a string of JavaScript code. The Function constructor can be used to create a function from string. Someone says that the Function constructor is another form of eval(). However, one significant difference between eval() and the Function constructor is that while eval() keeps the lexical scope, the Function constructor always creates top-level functions.

function f1() {
var bbb = 2;
eval('alert(bbb);');
}
f1(); //alerts 2

function f2() {
var bbb = 2;
new Function('alert(bbb)')();
}
f2(); //bbb undefined error

function f3() {
var bbb = 2;
eval('function() {alert(bbb);}')();
}
f3(); //alerts 2

eval() inside a function body creates a closure while new Function() doesn't. This difference may not bother you for the whole lifetime. However, it happens to bother me once. It's about jQuery - a new type of JavaScript library. I'm using jQuery in my bookmarklet application. In order to make the code as unobtrusive as possible, I decided to put all my code including the jQuery code inside an anonymous function. It looks like this:

(function() {
//jQuery code
//my code
})();

In this way, even the jQuery object is just a local variable. The outside environment is completely unaffected. But $().parents(), $().children, $().prev(), $().next() and $().siblings() always fail in my code. These functions are created by the Function constructor in $.grep() and $.map().

// If a string is passed in for the function, make a function
// for it (a handy shortcut)
if ( typeof fn == "string" )
fn = new Function("a","i","return " + fn);

So they are all top-level and the identifier "jQuery" inside is resolved as window.jQuery which is undefined and the code fails.


We can implement an alternative to the Function constructor and use it within the lexical scope:

var createFunc = (function () {
var args = [].slice.call(arguments);
var body = args.pop();
return eval('function(' + args.join(',') + ') {' + body + '}');
}).toString();

function f4() {
var bbb = 2;
eval(createFunc)('alert(bbb);')();
}
f4(); //alerts 2
You can use eval(createFunc) just like new Function(), but you get the bonus lexical scope binding.
function f6() {
var add = function(a, b) {return a + b;};
return eval(createFunc)('x', 'y', 'return add(x, y);');
}
f6()(3, 5); //8

At last, I quote Douglas Crockford's words on eval() and the Function constructor


"eval is Evil


The eval function is the most misused feature of JavaScript. Avoid it.


eval has aliases. Do not use the Function constructor. Do not pass strings to setTimeout or setInterval. "