January 19, 2007

Several Controversial Points in Pro JavaScript Techniques

I'm previewing the the ultimate JavaScript book for the modern web developer. It's a great book. I strongly recommend you read it and I'm sure that you'll thank me. To make it even better, I'd like to point out and discuss several controversial points.

A side effect of the anonymous function scope induction trick

At the end of Chapter 2 >> Privileged Methods

Listing 2-25. Example of Dynamically Generated Methods That Are Created When a New Object Is Instantiated

// Create a new user object that accepts an object of properties
function User( properties ) {
// Iterate through the properties of the object, and make sure
// that it's properly scoped (as discussed previously)
for ( var i in properties ) { (function(){
// Create a new getter for the property
this[ "get" + i ] = function() {
return properties[i];
};
// Create a new setter for the property
this[ "set" + i ] = function(val) {
properties[i] = val;
};
})(); }
}
// Create a new user object instance and pass in an object of
// properties to seed it with
var user = new User({
name: "Bob",
age: 44
});
// Just note that the name property does not exist, as it's private
// within the properties object
alert( user.name == null );
// However, we're able to access its value using the new getname()
// method, that was dynamically generated
alert( user.getname() == "Bob" );
// Finally, we can see that it's possible to set and get the age using
// the newly generated functions
user.setage( 22 );
alert( user.getage() == 22 );

The example code won't work as expected. My test with Firefox 2.0.0.1 shows that the user.getname and user.getage are actually undefined. But window.getname and window.getage are there! The error is caused by the scope induction trick:
(function(){})(). Inside the anonymous function, the this variable somehow points to the window object! In the simplest case:

var o = {f: function() {(function(){alert(this === window);})();}}; o.f(); 
//alerts true (but false if you evaluate the whole line in Firebug 1.0b8)

Seems that the implementation treats anonymous functions as properties of the window object?


null, 0, ‘’, false, and undefined are NOT all equal (==) to each other


In Chapter 3 >> != and == vs. !== and ===


"...In JavaScript, null, 0, ‘’, false, and undefined are all equal (==) to each other, since they all evaluate to false... "

Listing 3-12. Examples of How != and == Differ from !== and ===
// Both of these are true
null == false
0 == undefined
// You should use !== or === instead
null !== false
false === false


Actually 0, '' and false all equal (==) to each other and null equals (==) to undefined but both null == false and undefined == false evaluate to false. This is reasonable because both null and undefined indicate "no value" while false is a valid value.


domReady Race Conditions


In Chapter 5 >> Figuring Out When the DOM Is Loaded
Listing 5-12. A Function for Watching the DOM Until It’s Ready

function domReady( f ) {
// If the DOM is already loaded, execute the function right away
if ( domReady.done ) return f();
// If we've already added a function
if ( domReady.timer ) {
// Add it to the list of functions to execute
domReady.ready.push( f );
} else {
// Attach an event for when the page finishes loading,
// just in case it finishes first. Uses addEvent.
addEvent( window, "load", isDOMReady );
// Initialize the array of functions to execute
domReady.ready = [ f ];
// Check to see if the DOM is ready as quickly as possible
domReady.timer = setInterval( isDOMReady, 13);
}
}

// Checks to see if the DOM is ready for navigation
function isDOMReady() {
// If we already figured out that the page is ready, ignore
if ( domReady.done ) return false;
// Check to see if a number of functions and elements are
// able to be accessed
if ( document && document.getElementsByTagName &&
document.getElementById && document.body ) {
// If they're ready, we can stop checking
clearInterval( domReady.timer );
domReady.timer = null;
// Execute all the functions that were waiting
for ( var i = 0; i < domReady.ready.length; i++ )
domReady.ready[i]();
// Remember that we're now done
domReady.ready = null;
domReady.done = true;
}
}

Notice that in the domReady function, isDOMReady is added as a handler of the "load" event of window. The purpose of this is to take advantages of browser caching cabability to gain extra speed. However, the extra gain here causes troubles. When I tried to use this domReady implementation in a GreaseMonkey user script, sometimes the onDOMReady handler gets triggered twice. It isn't always reproducable. But if you refresh the page 15 times, you can see the double triggering problem one or twice. The only possible cause is the addEvent line. So I commented out the line and tested again, as expected, everything went OK.


I looked carefully at the code to find a possible race condition in function isDOMReady. The function


  1. Checks domReady.done
  2. ClearInterval and call handlers if DOM is ready
  3. Mark domReady.done true
When a page gets cached by browser, the window "load" event and an interval event almost occur at the same time, resulting two threads of isDOMReady executing side by side. In case that one thread is in step 2 but before step 3 while the other is reaching step 1, the later will read domReady.done as false and proceed to step 2, causing every handler triggered a second time.

There are two ways to work around


  1. Remove the addEvent line and be happy without the extra speed gain
  2. Advance the domReady.done = true; line as early as possible (may reduce but can't eliminate race conditions)

Update Tue, 06 Feb 2007 09:16:03 GMT window.onload reopened


The domReady() function above will prematurelly execute the handler if document.write() is used. The document ready solution in jQuery is so far the most robust. But in IE, premature execution will occur if innerHTML modification is performed before the document finishes loading. So the window.onload problem is now reopened. Great effort has been made to solve the problems.


0 comments: