About Me

I'm just someone struggling against my own inertia to be creative. My current favorite book is "Oh the places you'll go" by Dr. Seuss

Thursday, August 6, 2009

Another javascript pitfall: Hoisting

The problem is that, whether you realise it or not, javascript invisibly moves all the var declarations to the top of the function scope.

so if you have a function like this


var i = 5
function testvar () {
alert(i);
var i=3;
}
testvar();


the alert window will contain undefined. because internally, it's been changed into this:


var i = 5
function testvar () {
var i;
alert(i);
i=3;
}
testvar();

this is called "hoisting". The reason Crockford so strongly advocates var declarations go at the top, is that it makes the code visibly match what it's going to do, instead of allowing invisible and unexpected behavior to occur. function definitions are also hoisted to the top of the scope.

Putting a var inside an if statement is not against "the rules" of the language, but it means that, because of var hoisting, that var will be defined regardless of whether the if statement's condition is satisfied. Javascript also does not have block scope, so declaring a variable inside a block is doubly confusing to those who come from languages that do have block scope, because of hoisting.

Keep in mind also that hoisting does not include the assignment, so the var declarations will be moved to the top, and left undefined until they're assigned later, as in the example above.

This is a feature that must have seemed like a good idea at the time, but it has turned out to be more confusing than helpful.

8 comments:

TNO said...

Hence the introduction of let definitions in JavaScript 1.7:

https://developer.mozilla.org/en/New_in_JavaScript_1.7#let_definitions

Anonymous said...

Crockford has pointed to 2 important facts regarding hoisting but not all assertions on this page is correct

'Javascript also does not have block scope, so declaring a variable inside a block is doubly confusing to those who come from languages that do have block scope, because of hoisting.'

This is not entirely correct since javascript has block scope within functions (but not in other types of blocks):

function outerFunc()
{
var i = 5;
function innerFunc() {
alert('innerFunc i before redeclared in if: ' + i + ' (outerFunc i already masked by hoisting!!)');
if (!i) { var i = 3; }
alert('innerFunc i after redeclared in if: ' + i);
}
alert('outerFunc i before calling innerFunc(): ' + i);
innerFunc ();
alert('outerFunc i after calling innerFunc(): ' + i);
}
outerFunc();

Javascript have block scope within functions and a var is visible for the entire containing block (function)
Moving var-declarations to the top of the block ensures this visibility (hoisting)
Redeclaring vars inside contained blocks is allowed and practical but using initial value for a var declared outside containing block followed by a redeclaration seems illogical to me

Nevertheless

Crockford reveals the fact that redeclaring a var within a nested function masks all occurences previously declared from start to end in the nested function - NOT from the declaration point
He also reveals that hiding a declaration within other types of blocks (if, while, for, switch etc.) is pointless because of hoisting

I also agree that vars should be declared where they actually are declared by the browser

Breton Slivka said...

@anonymous, I do not believe that I am incorrect.

Functions are not blocks syntactically, nor do blocks inside of functions magically obtain scope, by the mere fact that they occur inside functions.

Though it doesn't seem to me that you are disagreeing with me so much as you are confused about the correct names for syntactical structures in javascript. In which case, yes, functions themselves do have scope, this was made abundantly clear in my original post, I should hope.

This can be confusing especially, since javascript's function syntax uses the curly brackets {}. However this is a special case where the curly brackets do not represent a block- they represent the function body, an entirely different thing from a block, not least of which because of the scoping rules.

For instance, an if(cond), or a for(init;cond;inc) can be followed by a single statement without the {}.
for instance.

if(x===true) alert("true");

works just fine. However, a function cannot have a body without the curly braces.

function myfunc (x) return x;

doesn't work.

I hope this points you at least, in the direction of investigating this yourself further. I'm sure you will discover that I am correct- and function bodies are not blocks.

trosen said...

Anonymous now trosen

Excuse me for using the word 'incorrect' - you are not.
My contribution was an attempt to clarify for others that more than 1 scope exists within a document.
I agree to the fact that a function body is a special type of block.
I also agree to the fact that this may be confusing, especially based on the definition given by:

https://developer.mozilla.org/en/Core_JavaScript_1.5_Guide:Block_Statement

A block statement is used to group statements. The block is delimited by a pair of curly brackets
...
...
Important: JavaScript does not have block scope. Variables introduced with a block are scoped to the containing function or script, and the effects of setting them persist beyond the block itself. In other words, block statements do not introduce a scope


No exceptions are mentioned in the block-definition despite the fact that the required pair of curly brackets around a function body is a 'closure', not a 'block statement'. Confusion wold have been avoided with a link to: https://developer.mozilla.org/En/Core_JavaScript_1.5_Reference/Functions_and_function_scope

In Variables introduced with a block are scoped to the containing function or script, .. or script is wrong.

eg.: Includ script1.js and script2.js in a html-document having document.body.onload = init;

script1.js:
var script1ScoopeVar = 'script1ScoopeString';
function script1ScoopeFunc() { alert('In script1ScoopeFunc(), script2ScoopeVar = ' + script2ScoopeVar); }

script2.js:
var script2ScoopeVar = 'script2ScoopeString';
function script2ScoopeFunc() { alert('In script2ScoopeFunc(), script1ScoopeVar = ' + script1ScoopeVar); }

function init()
{
script1ScoopeFunc();
script2ScoopeFunc();
}

Output:

In script1ScoopeFunc(), script2ScoopeVar = script2ScoopeString
In script2ScoopeFunc(), script1ScoopeVar = script1ScoopeString

Michael Haufe ("TNO") said...

@Slivka:

Since JavaScript 1.8 you are allowed to leave off function braces if the body consists of a single expression:

function(x) x * x;

Breton Slivka said...

@TNO: I know :), But I thought I would be complicating matters if I mentioned it, especially since it's no a standard art of ecmascript. I don't think function expressions were added to ecmascript 5, were they?

Michael Haufe ("TNO") said...

@Slivka:
No, it was not added to ES5. I had a short conversation with Brendan Eich on the matter recently, but its off-topic to your post:
https://mail.mozilla.org/pipermail/es-discuss/2009-August/009692.html

Anonymous said...

Thanks - awesome post! I never really knew about hoisting before today. Also read this article which is good:

Javascript Hoisting