In today’s tutorial, I’m going to educate you on the pitfalls of JavaScript, so we can be a better JavaScripter together. We’re going to talk about:
- Global variables
- Immediately self-invoking anonymous functions
- Function scope
this keyword
- Hoisting
- Programming style
- Prototyping
- Chaining
In this article, I will assume you have a basic knowledge of programming and have fiddled with JavaScript before.
When we think of JavaScript, we mostly think of development for the web browser. There are other applications of JavaScript, but to keep it simple, we’ll stick to behavior that’s native to web browsers.
Global variables
When we talk about global variables, we talk about variables that get inserted into the root object of the current environment. In the browser, that is the window object. This means that whenever you try to access some global variable, you can do so through window.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| myVar = 'Hello there, dear visitor';
console.log(myVar); // 'Hello there, dear visitor'
var myFunction = function(){
console.log(myVar); // 'Hello there, dear visitor'
console.log(window.myVar); // 'Hello there, dear visitor'
};
myFunction();
var mySecondFunction = function(){
var myVar = 'This differs';
console.log(myVar); // 'This differs'
console.log(window.myVar); // 'Helo there, dear visitor'
};
mySecondFunction(); |
Right at the start, we’ve tackled some behavior unique to window. Everything that can be accessed through window, can be accessed directly in every scope, unless it’s been redeclared, in wich case the locally declared variable will be used.
Lines 1,3,6,7 and 15 use a global reference.
Line 14 used a local reference, defined at line 13 in the local scope of mySecondFunction.
To make sure we don’t pollute the global scope of our application, we always precede our local variables with a var declaration.
var myThirdFunction = function(){
var localVar = 'This one is local';
someVar = 'Hello World!';
}
console.log(someVar); // 'Hello World!'
console.log(localVar); // undefined
The benefit is that these local variables don’t overwrite existing variables.
Let’s get a real world example.
File: calculator.js
kilo = 1000;
var Calculator = function(i){
this.value = i || 0;
this.k = function(v){ var v=v||this.value; return kilo*v; };
};
File: translate-metrics.js
ton = 'Ton';
kilo = 'Kilo';
milli = 'Milli';
Example: implementing calculator.js
c = new Calculator(12);
// Let's calculate what a thousandfold of these values looks like
m = c.k(); // 12000
n = c.k(13); // 13000
c.value = 14;
o = c.k(); // 14000
But when we combine it with translate-metrics.js, we set the global variable kilo to a string and thus our Calculator.k method will return NaN, which tells us that it tried to calculate something that was Not a Number.
Immediately self-invoking anonymous functions
So, how do we prevent JavaScript from leaking information between libraries? We do this using self-invoking functions or even anonymous self-invoking functions.
JavaScript has a great way of parsing itself, before handing over the result to the next function. This means we can do a lot of cool stuff like chaining and prototyping, but I’ll talk about those later on. What this also enables us to do is directly invoking a function after it’s been declared.
var myFunc = function(){
console.log('I\'m running wild here!');
}(); // 'I\'m running wild here!'
As you can see, I just declared a function that outputs a line to the console and directly after declaration, I invoke it using (), rather than issuing myFunc(); separately.
The thing is, we could also do that with an anonymous function, like so:
function(){ console.log('Hello'); }(); // 'Hello'
Because JavaScript allows us to place ( ) around pretty much everything, we make use of that to create a function without giving it a name. Thus, the immediately self-invoking anonymous function.
(function(){ console.log('Hello'); })(); // 'Hello'
And finally, this enables us to declare variables that don’t pollute the global scope.
(function(){
var myLocalVar = 'Hello';
console.log(myLocalVar); // 'Hello'
})();
console.log(myLocalVar); // undefined
Yet inside the anonymous function, we can still make use of the global variables and even declare functions to the global scope. That’s what the window object is all about.
Function scope
Now comes the tricky part. If you’re used to a language like Java or PHP, you’re working on a block scope base. JavaScript, however, uses a function scope.
PHP: Block scope
<?php
$variable = 'Hello';
function myFunc(){
echo $variable; // undefined
}
function myFunc2(){
global $variable;
echo $variable; // 'Hello'
}
myFunc();
myFunc2();
?>
JavaScript: Function scope
var variable = 'Hello';
var myFunc = function(){
var internal = 'Hiya';
console.log(variable); // 'Hello'
var internalFunc = function(){
var deepInternal = 'Howdy';
console.log(deepInternal); // 'Howdy'
console.log(internal); // 'Hiya'
console.log(variable); // 'Hello'
}();
console.log(deepInternal); // undefined
}();
As you can see, all the children of the current scope can access what’s in the local scope and in the parent scope, up until the root scope. That, however, works in one direction, as the parent cannot access a variable inside the child scope, unless it gets specifically returned by return.
What you also need to know is that if you close a scope, all the variables that belong to that scope are no longer accessible.
(function(){
var someVar = 12;
window.myFunc = function(){
console.log(someVar);
};
myFunc(); // 12
})();
myFunc(); // gives a reference error, stating that window.someVar was not declared
The reason why this happens is because someVar was accessible in the anonymous function and the first time we invoke the function, it was still within that scope. Now, when we invoked it later on, since it was declared to the window object, it no longer sees that variable in the current scope and thus references to something that no longer exists.
this keyword
The behavior above can be explained by the behavior of the this keyword. You could rewrite someVar as this.someVar. Now, what is this? this refers to the current scope of the function or object you’re working with. That is really useful when you don’t know the context in which your code will be applied. It is intensely used when one creates an Object in JavaScript.
var Meeting = function(host,guest){
this.host = host || new Person();
this.guest = guest || new Person();
this.handShake = function(cb){
console.log(this.host.shakeHand()+' I am the host of this meeting.');
console.log(this.guest.shakeHand()+' I am the guest of this meeting.');
if(typeof cb == 'function'){
cb(); // Call this function whenever the handshake is done.
}
};
};
var Person = function(name,age){
this.name = name || 'John Doe';
this.age = age || 0;
this.shakeHand = function(){
return 'Hello, my name is '+this.name+' and I\'m '+this.age+' years old.';
};
};
var bob = new Person('Bob',40);
var amy = new Person('Amy',38);
var meeting = new Meeting(bob,amy);
meeting.handShake();
This simply outputs:
Hello, my name is Bob and I'm 40 years old. I am the host of this meeting.
Hello, my name is Amy and I'm 38 years old. I am the guest of this meeting.
But what if we play around with this nifty callback function we implemented, so we can run things when the handshake was done?
First, let’s set up a couple of meetings.
meetings = [
{
h:{
n:'Bob',
a:40
},
g:{
n:'Amy',
a:38
}
},
{
h:{
n:'Janet',
a:42
},
g:{
n:'Peter',
a:26
}
},
{
h:{
n:'Marlin',
a:32
},
g:{
n:'John',
a:38
}
}
];
for(var i=0;i<meetings.length;i++){
var m = meetings[i];
var host = new Person(m.h.n,m.h.a);
var guest = new Person(m.g.n,m.g.a);
var meeting = new Meeting(host,guest);
meeting.handShake();
}
Output:
Hello, my name is Bob and I'm 40 years old. I am the host of this meeting.
Hello, my name is Amy and I'm 38 years old. I am the guest of this meeting.
Hello, my name is Janet and I'm 42 years old. I am the host of this meeting.
Hello, my name is Peter and I'm 26 years old. I am the guest of this meeting.
Hello, my name is Marlin and I'm 32 years old. I am the host of this meeting.
Hello, my name is John and I'm 38 years old. I am the guest of this meeting.
(function(){
for(var i=0;i<meetings.length;i++){
var m = meetings[i];
var host = new Person(m.h.n,m.h.a);
var guest = new Person(m.g.n,m.g.a);
var meeting = new Meeting(host,guest);
meeting.handShake(function(){
console.log(this.i); // undefined
this.handShake(); // repeats the handShake, without callback
});
})();
So, that’s rather annoying. When you have a good look at it, you’ll notice that the callback function is actually declared in another scope then where it’s executed, but that’s something we can do by mistake really easily in this case. So, how do we fix this?
(function(){
for(var i=0;i<meetings.length;i++){
var parent = this;
var m = meetings[i];
var host = new Person(m.h.n,m.h.a);
var guest = new Person(m.g.n,m.g.a);
var meeting = new Meeting(host,guest);
meeting.handShake(function(){
console.log(parent.i); // 0 || 1 || 2
});
})();
This simply puts the this object into a variable and passes this variable on to the function in the callback. One of the implementations of myself that had this issue, just recently:
...
Storage.prototype.script = function(k,f){
if(!this.scriptNodeExists(k)&&!this.injectScriptNode(k)&&Xhr!==undefined){
var x = new Xhr,t=this;
x.parentScope=this;
x.callback = function(){
t.setItemEncoded(k,x.response);
t.injectScriptNode(k);
};
x.get(f);
}
}
...
The problem was that setItemEncoded and injectScriptNode are methods I made for the Storage object and not for the Xhr object. I am, however, running a callback function that needs to execute functions of this very same object, whenever the Xhr object finishes getting the file from the server.
Thanks to great support from Pepkin88 in the comments, here’s a simple problem that displays the behavior of this. I made some minor improvements, but props to Pepkin88.
[jsfiddle url="http://jsfiddle.net/johmanx10/hmn76/12/" height="770px" include="js,result"]
Hoisting
You’ve probably read or heard about good programming habits concerning JavaScript. It was mentioned that it’s a good habit to declare variables at the top of a function, but why exactly? Well, that’s because JavaScript loves function and variable hoisting.
No, we’re not talking about some pirate that’s hoisting your goods. JavaScript has this behavior of declaring variables at the top of the scope. Now, what implications does this have?
var someVar = 'Hello';
(function(){
console.log(someVar); // Hello
})();
(function(){
var someVar = 'Hiya';
console.log(someVar); // Hiya
})();
(function(){
console.log(someVar); // undefined
var someVar = 'Howdy';
})();
Huh? So, how does that work? Well, JavaScript has some behavior in the background that hoists var declarations to the top of the scope. That is why you can invoke functions you declare later on in the code, as long as you don’t run the function that invokes them before they are declared. This, however, on the background renders the following JavaScript for the last function:
(function(){
var someVar;
console.log(someVar); // undefined
someVar = 'Howdy';
})();
And now you can clearly see that the value of someVar has been reset and not yet defined.
Programming style
It has been a holy war between programmers. Some people like their brackets left, others right. Some people like indentation with tabs, others with spaces. Some like 2 spaces, others like 8 spaces. I like programming with my brackets right and 4 spaces indentation. That has been really different in the past, when I was only focusing on PHP. I program with 2 monitors of 24″ side-by-side, so my indentation used to be really optimized for readability. It was:
function()
{
$var = 'hello';
return $var;
}
This got some critique really early on by fellow programmers, but I kept on using it like this, until the unexplainable bugs started happening. JavaScript has a .. ahum .. really neat engine that automatically fixes your broken code, where you forgot your semicolon. Really great for starting programmers. Not so great for professional development. So, what happens?
(function(){
return
{
a: 12,
b: 13
};
})(); // undefined
How can this be undefined? It’s the only thing in the fracking function, right? Well, what happens is the engine puts a semicolon behind the return, which in turn returns not quite as much as we would like. The object that get’s declared afterwards will be skipped, because return breaks the function.
So, it’s good behavior to put your brackets on the right, so the engine won’t break your code for you.
Prototyping
Now here comes the power of JavaScript. Everything in this beautiful language is extendable. Even the objects and functions that come packed with your environment. So, what is prototyping?
Everything, except for values and expressions, in JavaScript has a prototype. And although a value has no prototype, the Object it originates from does. The prototype can redefine how an Object behaves and can add functionality to the Object. Even the prototype itself has a prototype, which has a prototype, and so on, until the root is reached.
var arr = [1,2,4,5,33]; // This originates from the object Array
Array.prototype.sum = function(){
var sum = 0;
for(var i=0;i<this.length;i++){
sum += this[i];
}
return sum;
}
console.log(arr.sum()); // 45
Since arr originated from the object Array, we can add functionality to it through changing the prototype of Array. Now we have a function to calculate the total sum of the numbers inside the array. Prototypes can also invoke methods that were declared inside the prototype as such:
Array.prototype.avg = function(){
return this.sum() / this.length;
};
console.log(arr.avg()); // 9
That’s great for calculating your average income and expenses.
Prototyping has effect on all the variables that originated from that object, even if they were declared before the prototype changed.
n = new Number(12);
console.log(n.valueOf()); // 12
// Only works with positive numbers. Just for demo purposes
Number.prototype.power = function(n){var n=n||2,r=this.valueOf();n--;for(var i=0;i<n;i++){r*=this.valueOf();};return r;};
n.power(); // 144
n.power(2); // 144
n.power(3); // 1728
And now we can do some basic math function without issuing the Math object.
Let’s note that simply prototyping may brick third party code, since it may have unexpected behavior. Libraries are almost always built with the underlying thought that the prototype of core functionality stays untouched. In that respect, you should always check if the object exists and if the method you try to prototype wasn’t already declared.
var Proto = function(){
this.extendable = function(o,m){
return (window[o]!==undefined&&window[o][m]===undefined&&window[o].prototype[m]===undefined);
}
this.exists = function(o,m){
return (window[o]!==undefined&&(window[o][m]!==undefined||window[o].prototype[m]!==undefined));
}
this.extend = function(o,m,f,d){
if(this.extendable(o,m)&&(d===undefined||this.exists(o,d))){
window[o].prototype[m]=f;
}
}
return this;
};
// If Array exists and has no method sum
Proto().extend('Array','sum',function(){
var sum = 0;
for(var i=0;i<this.length;i++){
sum += this[i];
}
return sum;
});
// If Array exists, has no method avg and has method sum
Proto().extend('Array','avg',function(){
return this.sum() / this.length;
},'sum');
At first we made an Object called Proto. This checks if the prototype is extendable and if so, extends upon the prototype. Later on, we create the method Array.prototype.sum which enables us to do the sum function on arrays. Finally we create the Array.prototype.avg method, which depends on Array.prototype.sum. If the dependency exists, it gets declared and because it does, we now also have the avg method to use on arrays.
We could improve this a lot. For instance, we could allow inter-Object dependencies and check multiple dependencies instead of one, but for the sake of the tutorial, I’m not going to build that extra functionality in here.
Chaining
The Proto object we just made was also an example of a short chain. What we did was execute a function, return this and move on with the result of that. The reason we can chain in JavaScript is because it executes and parses functions on the fly, so you can select the result directly in the same line of code. Two that are used a lot in general web development are:
document.getElementByTagName('script')[0];
and
var e = function(e){return document.getElementById(e);};
As you can see in the first example, we first call the document object, ask it to run the method getElementByTagName with the parameter script. This gives us an array of Nodes that fit the tagname of script. From that array, we take the first item by issuing [0] (zero indexing).
A library that makes really good use of this behavior is jQuery.
jQuery example:
$('ul').children('li').each(function(){
$(this).hide().fadeIn('slow').addClass('fade-in');
});
And it’s near endless how far jQuery let’s you chain up functions. Now, how is this possible?
jQuery stores the selected item that was defined using $() inside the corresponding object. It then passes that item on to the method we called, children, which in turn returns that same item, or the result of that item. In any case, it returns a jQuery object. And that is the interesting part, since the return value can be called again using JavaScript, due to it’s behavior. So now when the method children finishes, it returns a jQuery object. That jQuery object has all the same methods the previous jQuery object had, so all methods are available yet again.
$('body') //returns jQuery object
.find('#head') //returns jQuery object
.addClass('js-enabled') // returns jQuery object
.html('Hello'); // returns jQuery object || String object
But we can do that too and on a really small scale as well.
Combining previous code:
[12,3,0].avg().power(3); // 125
First we have an array. That originates from Array. Array has the prototype method avg. That in turn returns a number, which originates from Number. Number has the prototype method power and with the parameter of 3, it does Number^3.
The sum of the array is 15. The length of the array is 3. The average is 5. 5^3 = 5 * 5 * 5 = 125
Conclusion
JavaScript is a really useful and powerful language, that has been misunderstood by many, due to it’s alternative behavior that leads to a lot of bugs and frustrations. Nonetheless, JavaScript is a real mature language that helps us developers out on many occasions. With the ever growing number of stable and helpful libraries like jQuery, MooTools and Dojo, we see a real growth in support and enthusiasm for the language.
JavaScript has a somewhat high learning curve, even for seasoned programmers, but can be a really easy-going language once one is familiar with the quirks. I hope this article helps overcome those quirks and lowers the border for programmers that are new to JavaScript or just haven’t used the language to it’s full potential.