Saturday, May 19, 2012

Naked JavaScript - Function Scopes, Hoisting And Other Quirks

In this part 7 of the series Naked Javascript, I am going to cover a few interesting quirks about functions and scoping in JavaScript and also talk about a strange feature called hoisting.

The first thing that one needs to know is that in JavaScript, we dont have block level scopes. Instead, in JavaScript, functions are responsible for creating new blocks of scope. Let me elaborate. In other programming languages like java, if you write a block of code like this

public void someMethod(){
for(int i=i;i<10,i++){
 //Some usage of variable i
}
//Compiler error
System.out.println(i);
}


When you attempt to complie this program in Java, you are presented with an error at the line where you attempt to print the value of the variable 'i'. Thats because, once you are outside the for loop, you cannot access the variable 'i'. The scope of the variable 'i' lies only within the code block in which it was declared in Java. Cominng to JavaScript, you need to understand that although javascript makes use of code blocks in the same way that Java does, the scope of a variable lies throughout the function in which it was declared. This means that the following code will successfully print 10 when executed in JavaScript.

public void someMethod(){
for(int i=i;i<10,i++){
 //Some usage of variable i
}
//Prints 10
console.log(i);
}


In the above code, although we printed the value of the variable after it was declared and initialized ,but outside the curly braces, it worked perfectly fine. Now lets take another example.

var n = 1;

function someRandomFunction() {
    console.log(n); // This prints 1.
}


In this function, we declared a variable outside the function. And were were easily able to print its value inside the function, due to closures. Now consider the following piece of code.

var n = 1;

function someRandomFunction() {
    console.log(n); // This prints undefined.
    var n = 100;
    console.log(n); // This prints 100
}


Strange as it may seem, the first console ouput prints undefined. But why? Why is it not able to see the declaration of the variable 'n' that was part of the closure like the earlier example? The reason behind this odd behavior is a feature called 'hoisting'. Hoisting can be explained in a very simple way. For any variable declared within a scope, JavaScript separates the variable declaration and the variable initialization and hoists the declaration to the first line of the scope and initializes the variables at the appropriate lines at which they were initialized by the programmer.

Based upon the above explanation, this is what JavaScript saw when it ran our program.

var n = 1;

function someRandomFunction() {
 var n; //The variable declaration hides under harry potter's cloak. So you cant see it.
    console.log(n); // Now you know why its undefined here
    n = 100; //Now the variable gets initialized.
    console.log(n); // And prints out 100 here.
}


As you see in the code above, the variable was undefined because it was shadowed by the local declaration of the variable in the function. (At least they got that part right!). But then, enter functions! And you have some more nasty code to deal with.

Check out the following piece of code wherein we declare a variable, a function using the conventional function declaration and a second function using the variable style declaration.

function hoistingContainer(){
 console.log(n);
 
 innerOne();
 innerTwo(); //Throws an exception
 
 var n = 100;
 
 function innerOne(){
  console.log('innerOne');
 }
 
 var innerTwo = function innerTwo(){
  console.log('innerTwo');
 }
 
 //This will execute but the code never
 //reaches here because of the previous exception
 innerTwo();
    
}


In the above example, make a note of the different ways in which we declared the two functions. If you run the above code, you would find that the first invocation of the function innnerTwo() leads to an exception whereas the invocation for the function innerOne executes normally. The only difference between these two functions was the way in which they were initialized. Perhaps it can be concluded that when functions are explicitly declared using a variable instead of the conventional notation, the initialization is not hoisted. However, using the conventional function declaration notation, even the function body gets hoisted along with the function declaration.

This implies that if you define functions using the standard notation instead of the variable notation, you can create functions anywhere within a scope and use them at any point within the same scope irrespective of their point of declaration.

The next interesting thing that I want to discuss about is what could happen if you accidentally forget to use the 'var' keyword when declaring a variable inside a scope. Usually, when you declare local variables inside a function, you would use the var keyword. Only the variables that are declared using the var keyword become function local. However, if you accidentally forget the var keyword, javascript will not complain, instead it simply creates a property of the same name in the global scope i.e. on the 'window' object. And as we are already aware, this can have disasterous consequences.

Take a look at the follwing code.

window.constant = 10;

//prints 10, the constant from the global
//window object, 
console.log(constant);

function outerFunction(){

 var name = 'xyz';
 
 //Missing var keyword
 constant = 20;
}

//Invoke the function
outerFunction();

//Prints undefined since name was 
//locally scoped in the function
console.log(name);

//Prints 20, because its value was
//reassigned inside the function
console.log(constant);


Moral of the story, dont forget to use the 'var' keyword when declaring local variables inside a scope.



Scopes and closures.


Closures take extensive advantage of the scoping feature of JavaScript. When a function is declared within the scope of another function, the inner function maintains 'hidden references' to the varibles in the scope in which it was declared. You may also think of it as if functions have their own scope memory. Functions are behaviour. But Javascript functions can not only contain behaviour but also act as a data store. You may like to read another article of mine in which I have elaborated on closures with a couple of examples.



Semicolon insertion.

This is a really strange feature in javascript. The JavaScript processing engine tries to act smart and detect if a semicolon is missing where it should have been put. If it is found to be missing, it simply inserts the missing semicolon and executes the code without any warning. The simplest example, as demonstrated by many other is the problem of semicolon insertion with the return statement. The follwing code returns undefined.

function badStyle(){
 return
 {
  name:'value'
 };
}


function goodStyle(){
 return {
  name:'value'
 };
}

//Runs as expected
console.log(goodStyle());

//Prints undefined
console.log(badStyle());


The second line prints undefined because, since javascript expected that return statement be terminated by a semicolon, it inserted one when it did not see any in the badStyle function. In the goodStyle function, it did not insert one because before the line termination, it encountered a symbol for object literal declaration.

Most of the problems that arise due to the above discussed quirks of the language can be avoided by writing simple yet decently formatted code. Variable declarations at the top, function declarations before using them, and proper opening and closing of curly braces and always adding a semi colon when you are terminating your statements wont only avoid these issues but also make your code readable and understandable. Who knows, just spendinng just a couple of minutes restructuring your code may later save you hours of debugging. 

Stay tuned. There's more to come!

Signing Off!
Ryan

No comments: