
I am currently reading the MEAP of John Resig’s (@jeresig) Secrets of the JavaScript Ninja and one of the things that many beginners and even intermediate JavaScript developers do not understand completely, is the different ways in which functions can be defined and how scope differs depending on how you defined your function.
It is absolutely essential to grasp this concept as it opens many doors for the developer once this is understood. Whilst reading the above title I myself finally grasp the concept and wish to share the code example that made me see the light, so to speak.
First, there are a couple of ways to define a function in JavaScript and here we will look at three of those:
function isNimble() { return true; }
var canFly = function() { return true; };
window.isDeadly = function() { return true; };
The first of the examples above is the traditional way of defining functions and for most, this will be very familiar. Then there is the two other ways to define your functions. The first assigns the function to a variable, in this case canFly, which means that if you run:
window.onload = function() {
canFly();
}
The result will be true. The third line assigns the function to the variable isDeadly, which it attaches to the global window object. You will notice another syntactic difference between the first and the last two, that is that the latter is terminated at the end with a semicolon where the first is not.
If I wrote the following, it would not seem strange that the line will be terminated by a semicolon:
var canFly = "true";
In the above the only difference is the value of the variable is the function itself. Now, JavaScript being a functional programming language, functions are treated as first class citizens. However, even though canFly and isDeadly can by called by name, the actual function is anonymous.
And in that lies the difference. Have a look at the following code:
window.onload = function() {
assert(typeof isNimble() == "undefined", "Will this pass or fail?");
assert(typeof canfly == "undefined", "Will this pass or fail?");
assert(typeof isDeadly == "undefined", "Will this pass or fail?");
function isNimble() { return true; }
var canfly = function() { return true; };
window.isDeadly = function() { return true; };
}
The assert method is very simple and is used merely to log out the results. Below is the assert function:
function assert(value, desc) {
var li = document.createElement("li");
li.className = value ? "pass" : "fail";
li.appendChild(document.createTextNode(desc));
document.getElementById("results").appendChild(li);
}
You can assign some CSS to the pass and fail classes to color the results accordingly. Now, before you run the above code, let’s have a look at it. We are calling the assert function three times and each time we are asking the same question, is the key on the left equal to undefined.
Now, as you may have seen in your JavaScript coding, if a variable or function does not exist the browser will throw and exception that will tell you x is not defined. So in the above we expect that all of the assertions will pass because the functions are only defined after we call assert.
What do you think? Will they all pass i.e. each of the left hand arguments will evaluate to “undefined”? If you answered yes, you are unfortunately incorrect. Earlier I mentioned that functions are first class citizens but in that there is also a catch. So go ahead and run the code.
window.onload = function() {
assert(typeof isNimble() == "undefined", "This will fail! As named functions are always defined.");
assert(typeof canfly == "undefined", "Anonymous functions do not get the same benefit.");
assert(typeof isDeadly == "undefined", "Anonymous functions do not get the same benefit.");
function isNimble() { return true; }
var canfly = function() { return true; };
window.isDeadly = function() { return true; };
}
So why does the above happen? Simple, anonymous functions does not get the same respect as named functions. But wait, none of these functions are anonymous, are they? In fact the last two are, while the return value is assigned to the variable name the function itself is anonymous.
And because of this, as with variables to which a simple string or boolean is assigned, the variable will remain undefined until after, for example line 7 above, at which point canFly will be in scope and be defined. So changing the above code as follows will make the second assert ‘fail’.
window.onload = function() {
var canfly = function() { return true; };
assert(typeof isNimble() == "undefined", "This will fail! As named functions are always defined.");
assert(typeof canfly == "undefined", "Anonymous functions do not get the same benefit.");
assert(typeof isDeadly == "undefined", "Anonymous functions do not get the same benefit.");
function isNimble() { return true; }
window.isDeadly = function() { return true; };
}
Update: To further expand the above and perhaps make it a little clearer I recorded the video below to demonstrate executions of the script using Firebug:
So, named functions will always be defined and so where they are defined does not matter in relation to where they are called. Anonymous functions on the other hand will only be defined, and thus be in scope and callable, after the line on which they are defined.
The above seems so obvious but believe me, it trips up a lot of JavaScript developers. There is more to function and variable scope as well as anonymous functions but, understanding the above is going to go a long way in creating a solid foundation to build on going forward. I look forward to your comments.
P.S. Most of the code examples above are from the book Secrets of the JavaScript Ninja by John Resig.
