Saturday, November 17, 2007

Javascript scoping : Things to keep in mind

Because scope in javascript can be the source of very hard to track bugs, we will see how work the variable scoping in javascript. Depending on your programming languages background (block level scoping or not), you may have some surprises.

Defining the global space


In most of the actual browsers, the window object is the global space that contains all the core functions of the language and all the variables, functions that will be defined in it. In perl, this will be the 'main' namespace.

If you declare a variable, you will see that you can call it on the window object:
var mywidth=100;
myheight=200;
alert(window['mywidth']); // returns 100
// you could also write it like so : window.mywidth

alert(window['myheight']); // returns 200
alert(window['myheight']===myheight); // returns true

Calling variable or functions on the window object can be very useful when you take a function name as a string from an other function!
Let's see an example:
function getFunction(func) {
      func();
}
getFunction('myalert');

function myalert() {
   alert('I am going to fail!');
}
The above example will fail because we are trying to resolve what javascript considers as being a string into a function and obviously, this is not going to work!
We could then try to eval the string and change the getFunction:
function getFunction(func) {
     var ref=eval(func);
     ref();
}
This will work but eval'uating the string into a function has some overhead and security problems that we could get rid off by calling the function on the window object directly!
function getFunction(func) {
    window[func]();
}
The first example will then work!
Obviously not everything is accessible from the window object as we can induce scoping in javascript at a function level.

to var or not to var ?


In javascript there are two ways of defining a variable, with the keyword var or without!
The main thing to understand is that contrary to the abbreviation, var doesn't mean you are declaring a variable, it will indicate the scope of your variable!
(my keyword vs our keyword in perl) We've been using both notations in the above examples and we see that variables define in the global space do not have their scope change.
//the following notations are the same:
var myheight=100; 
myheight=100;
alert(window.myheight==100);//return true
In the global namespace, the var keyword doesn't have any influence on the scoping of the variable, therefore we can do the following:
var myheight=100;
mywidth=100;

function getwidthAndHeight() {
 alert(myheight+' and '+mywidth);
}
Variables defined in the global namespace will be accessible from within all the functions because they are hold in the window object.
The var keyword starts to be interesting only once use in a function context.
We can see in the example below that the use of var will change the scope of the variable:
var myheight=100;
mywidth=100;

function getwidthAndHeight() {
 alert(myheight+' and '+mywidth); 
 var mycolor='#000000';
 mybackgroundColor='#FFFF00';
}
function getColorAndBackgroundColor() {
   alert(mycolor+' and '+mybackgroundColor);
}
getwidthAndHeight(); //alert 100 and 100
getColorAndBackgroundColor() //alert undefined and #FFFF00
Here you need to be very careful to the getWidthAndHeight way of defining the variables.
the mycolor variable is defined using the var keyword while the mybackgroundcolor doesn't.
In fact, when a variable is defined without the var keyword within a function, in the bakcground the following will be interpreted:
function getwidthAndHeight() {
 alert(myheight+' and '+mywidth); 
 var mycolor='#000000';
 window.mybackgroundColor='#FFFF00';
}
Every variables that are not defined with the var keyword in a function context will be assigned to the window object and therefore becomes global variables too.
That's why you should always be aware of when use or not use the var keyword in a function.
A variable defined with the var keyword in a function is not accessible from outside, that's why we can refer to it as a private variable!
You can also create private functions within a function thanks to closure:
function getwidthAndHeight() {
 var mycolor='#000000';
 var mywidth=function () {
     return mycolor;
 }
 alert(mywidth());//alert #000000
}
//outside of the function
alert(mywidth()); // error!
The following variables, one holding a string and one a reference to function will not be accessible from outside and are private to the function they are defined in!

Reminder : Avoid global space variables


Javascript follows a very basic rule when use within a browser:
Last in, win!
Putting variables and even functions in the global space is to avoid as if you have the same variables or functions defined into 2 differents javascript files, the last javascript file will 'overwrite' the precedent functions and variables!
Therefore you might not be working with the function you were thinking of.
It is not very relevant if you only use two or three functions but when you add some libraries from outside and if they put functions or variables in the global name space, you will have a hard time to understand why you're not getting what you were expecting, thinking there is a bug in your function!!

Block level scoping


There aren't!
You have to be aware that variables define within the scope of block statement (for, while, if...) will be accessible outside of this block:
if(mywidth==100){
   var myheight=200;
}
alert(myheight); // alert 200
This behavior is not true in all languages where a variable defined within a scope will end its life once the block is terminated.
There is one thing you have to be aware of:
for(var i=0;i<=100;i++) {
   mycolor=i;
}
for(; i<50;i++) {
   mywidth=i;
}
alert(mycolor); //100
alert(mywidth); // error
The following might not give you back what you could expect!
defining the i variable with the var keyword in the for loop doesn't give the i keyword a scope to that loop.
Therefore when entering the second loop, i is equal to 100 and the second loop will never start!
In order to avoid this kind of problem, you should always use different name for counters:
for(var i=0;i<=100;i++) {
   mycolor=i;
}
for(j=0; j<50;j++) {
   mywidth=j;
}
This will avoid some unexpected bugs and make your life easier!
i,j,k,l,m,n you can just increment the name according to the alphabet letters.

Functions and scope


We have seen that the var keyword has only a meaning within the context of a function.
Therefore we can have privates and global variables if we want.
But functions allows to define an other scope: the name of the function!
We have seen that all variables define within a function without the var keyword were assigned in the background to the window object.
That's fine but let's say that you want to create clean code and avoid global variables but that you would like to use the variable from outside the function, how could we do?
In fact, outside of a function, in the global namespace, we can give to the window object an other name, an other keyword : this.
The this keyword is very important in javascript and can be confusing at the beginning but let's see the following code:
mycolor=100;
alert(this.mycolor);//return 100
alert(window.mycolor);//return 100
alert(window===this); return true
The this keyword in the global namespace is equal to the window object!
Functions allows us to use the this keyword within them but in that case, the this keyword do not refer to the window object anymore but to the function name!
But be careful, the way you call the function can switch back the this keyword to the window object!
function changeColor() {
  alert(this);
}
changeColor(); // object
new changeColor(); // object Object
Javascript is telling that the first call to the changeColor refers to a native object, that is the window object while the second object is a user defined Object, that is changeColor.
We can verify that the this keyword refers to the function name with the instanceof keyword that tells us if something corresponds to a certain name:
function changeColor() {
  alert(this instanceof changeColor);
}
changeColor(); // false
new changeColor(); // true
This may sound a little bit confusing but using the new keyword will create in the background for us, an object (like the window object but not native) and bind this object to the name of the function.
(In perl, you 'bless' an hash with its package name to get an object)

What can we do with this?
Well, now, we are able to create variables scoped to the name of the function!Giving us 3 types of scope for variables:
function scopeIsFun() {

    globalVariable=100;

    var privateVariable=200;

    this.publicVariable=300;

    this.publicMethod=function() {
         alert('You can call me on the scopeIsFun object!');
    }
    var privateMethod=function() {
        alert('you cannot call me from outside!!');
    }
}
alert(globalVariable); // 100

alert(privateVariable); //error

alert(scopeIsFun.privateVariable); //error

alert(scopeIsFun.publicVariable); // 300

alert(publicVariable); // error

alert(scopeIsFun.publicMethod()) // 'You can call me on the scopeIsFun object!'

alert(publicMethod()) // error

alert(scopeIsFun.privateMethod()); // error

alert(privateMethod()); // error


Conclusion


Javascript scoping may seem weird at the beginning but it can be a very powerful tool to build nice libraries that won't walk on other librairies.
The different ways to scope a variable or methods within a function is the base that will allow to simulate namespace in javascript and the base of object oriented programming.
We will see what is and how to simulate namespace by using what we've seen here in a next entry!

2 comments:

Anonymous said...

One thing to keep in mind with regards to global scope of variables is that Internet Explorer throws a bunch of stuff in the global scope for you. This will actually cause problems if you are not aware of it.

Check out the details here:
http://webbugtrack.blogspot.com/2007/09/bug-162-global-namespace-pollution-in.html

tom

shiriru said...

Hi tom,
Thanks for the link!
Very instructive!
It is true that one should not forget browser global space pollution!