Introduction
No doubt about it–JavaScript is absolutely essential for any web developer. Gone are the days when you could get by with a little CSS and HTML. Fortunately, it has so few rules that it’s dead simple to get started:
alert("Hello World."); // Shows the user "Hello World." in a dialog
That’s it! Many simple tasks can be accomplished with just a few lines of code. There’s no need to import packages, or declare namespaces. You write some code, and it runs. However, the very lack of structure that gives JavaScript its low barrier to entry also enables beginning developers to write unstructured, fragile code without realizing it. As an application grows, this unstructured code will come back to haunt you in the form of unexpected, difficult-to-find bugs.
In this tutorial, I plan to correct some of the common misconceptions and mistakes that cause undue pain for beginning JavaScript developers. Here are a few things every beginning JavaScript developer should know:
1. You can add properties to almost everything
JavaScript has only three primitive data types: String, Number, and Boolean. Everything else (if it’s not
null
or
undefined
) can have properties added to it.
Note: Even though String
is a primitive type ("hi"
), there is another incarnation of String
in JavaScript which is an object (new String("hi")
). See here for details.
You may have seen something like this:
var a = {}; // create a new object
a.b = 'hello';
In the above code,
a.b
meant nothing until I set a value to it. Now
a
has a property called
b
. But wait, it gets weirder.
var a = []; // create an array
a.b = 'hello';
Great, so now you’ve got an empty array, with a property on it called
b
.
Caution: Just because you can put properties on arrays doesn’t mean you should. As I’ll discuss later, the above code will change the behavior of for...in
loops on that array.
How about this?
var a = function() {};
a.b = 'hello';
Yes, that’s right.
a
is now a
function, with a property. Which brings me to my next point.
2. Functions are objects
More and more languages are getting support for treating functions as values, but depending on your background this may not be at all familiar to you.
function doIfTrue(isTrue, whatToDo)
{
if(isTrue)
whatToDo();
}
doIfTrue(true, function() {alert('hello');}); // alerts "world"
doIfTrue(false, function() {alert('world');}); // does nothing
The function above treats the
whatToDo
parameter as a function. This sort of pattern allows developers to do some very powerful things like setting up event handlers with very little code.
3. for...in
loops iterate over property names, not values
Developers coming from a Java or C# background are used to so-called “foreach” loops, which iterate over all the values in a collection. JavaScript doesn’t (currently) have an equivalent loop. The closest thing, a “for in” loop, has some important differences:
var arr = ['a', 'b', 'c'];
for(var i in arr) {
alert(i); // 0, 1, 2
alert(arr[i]); // 'a', 'b', 'c'
}
As you see, the variable used by the
for...in
loop gives you the
keys by which other values can be found, but you have to perform the extra step of getting the values off of the original object.
Why does JavaScript do this? The main reason is that
for...in
was not made for arrays: it was made for objects that have properties on them:
var pairs = {'a': 'apple', 'b': 'banana'};
for(var key in pairs) {
alert(key); // 'a', 'b'
alert(pairs[key]); // 'apple', 'banana'
}
Caution: Because a for...in
loop iterates over the properties on an object, you’ll get strange behavior if you use the loop on an array that has extra properties added to it, For this reason, you should avoid using for...in
loops on arrays–use simple for
loops instead. They’re faster anyway.
Note: ECMAScript 6 will introduce for...of
loops, which iterate directly over values.
4. Variable scoping
All developers, in every language, should avoid using global variables. But it’s easy to do by accident in Javascript because nobody’s forcing you to organize your code into modules.
var message = "hello world";
alert(message);
If the above code is run all by itself in the browser, it will create a new global property on the
window
object, called
message
. That means that if any other code on your site also has a similarly-declared variable called message, they’ll stomp on each other.
In Javascript,
all variables declared with the var
keyword are scoped to their declaring function. That means if you want to make sure your variables aren’t global, you should put them in a function, and then call that function.
(function() {
var message = "hello world";
alert(message);
})();
Developers with a background in other languages based on C syntax are used to variables scoped by curly braces (
{}
). In JavaScript, variables declared with
var
are “hoisted” to the top of the function they’re declared in. This code:
function sayHi() {
if(true) {
var s = "hi";
}
alert(s); // alert("hi") -- `s` is still within scope.
}
… is the same as this:
function sayHi() {
var s;
if(true) {
s = "hi";
}
alert(s);
}
This also means that if you refer to a variable outside the scope of your function, you’ll use the value of that variable
at the moment the code is run, not at the moment your function is created. This trips up beginners all the time:
var thingsToDo = [];
for(var i = 0; i < 2; i++) {
thingsToDo.push(function() {alert('hello ' + i);});
}
for(var k in thingsToDo) {
thingsToDo[k](); // alerts "hello 2" twice.
}
Remember that the variable
i
starts as a
0
value, but by the time it’s called (in the second loop) its value has been incremented to 2. To capture the value of
i
when you create the function, you have to create another function scope:
var thingsToDo = [];
function createHelloAlert(v) {
// `v` only exists within this method call.
return function() {alert('hello ' + v;}
}
for(var i = 0; i < 2; i++) {
thingsToDo.push(createHelloAlert(i));
}
for(var k in thingsToDo) {
thingsToDo[k](); // alerts "hello 0", then "hello 1".
}
Note: ECMAScript 6 will introduce a let
keyword, which will allow you to declare a variable scoped to the curly-braces. This is known as Lexical Scoping.
5. Variables that aren’t explicitly declared can be global
Suppose you remember to wrap your code in a function, but forgot the
var
keyword:
(function() {
message = "hello world";
alert(message);
})();
When you set a variable’s value, and you haven’t
declared it to be a variable for the current function scope via the
var
keyword, JavaScript assumes you mean
this.propertyName
. So the above code is the same as this:
(function() {
this.message = "hello world";
alert(this.message);
})();
If you’re a Java developer, you’re saying to yourself, “What’s this? No, really. What is
this
?” In other languages,
this
means the instance of the class that you’re looking at, but in JavaScript it means the object that your function got called on. If you’d done this:
var a = {
foo: function() {
this.message = "hello world";
}
};
a.foo(); // `a.foo()` means foo's `this` is `a`.
alert(a.message); // outputs "hello world"
… then calling the
foo
method puts a value on
a
’s
message
property. But since our original code snippet is calling a function that doesn’t have anything in front of it,
this
is given a fallback value of
window
. You end up creating another global variable just by forgetting the
var
keyword.
6. Understand how .prototype
works
JavaScript uses a special
prototype
property to solve the problems that other languages use classes to solve. Consider the following:
function Person(first, last)
{
this.first = first;
this.last = last;
}
var john = new Person("John", "Doe");
var mary = new Person("Mary", "Deer");
Person.prototype.full = function() {return this.first + " " + this.last;};
alert(john.full());
There are a lot of things happening here.
- We create a
function
, which will set properties on its this
object when called.
- We create two separate instances of that function by putting the
new
keyword before our function calls. This ensures that john
and mary
refer to completely separate objects, each with their own first
and last
properties.
- We create a new function and assign it to the
full
property on our Person
function’s prototype
property. The prototype
property exists on all functions, and allows you to define fall-back properties that should exist on every object created from that function.
- We call the
full()
function on john
. JavaScript sees that the john
object doesn’t actually have a full
function on it, so it looks for a Person.prototype.full()
function and calls that instead. Within that call, however, this
still refers to the john
object.
7. JavaScript never sleep()
s
Many languages support the notion of
threads, which allow you to have several procedures running at the same time. The problem is that multi-threading opens a huge can of worms: thread locks and other race conditions that even the best developers have a hard time grappling with.
JavaScript avoids these issues entirely by only allowing one piece of code to run at a time. This is great, but it requires us to write our code in a different way. Most languages create a layer of abstraction over operations where the program is waiting for something to happen. For example, in Java, you can call
Thread.sleep(100)
, and the rest of your procedure won’t run until 100 milliseconds have passed.
When you’ve only got one thread to work with, you can’t afford to have it doing nothing for hundreds of milliseconds at a time–it would freeze up the UI and make a horrible user experience. So JavaScript almost never blocks execution. Instead it relies on
asynchronous callbacks to let you say what should happen when an operation finishes. For example:
window.setTimeout(function() { console.log(a); }, 1000);
console.log('hello world');
var a = 'got here';
The above code will
first create a “hello world” message (even though that line of code comes after the
setTimeout()
method), and then print “got here” one second later. Even if we changed the timeout to
0
, we
know that the
a
variable will be initialized before getting logged because no other JavaScript can run until this code has finished.
Beginners will often write code like this, and then wonder why
a
doesn’t have a value:
var a;
$.ajax(url, {success: function(e, data) { a = data; });
console.log(a); // BAD! This line will run before `a` gets set to a value!
Don’t fall into that trap.
There are a few caveats that I should mention.
- Not all functions that you pass into other functions are asynchronous callbacks. The following code works just fine, because
doIfTrue()
calls its whatToDo
argument, synchronously, before it returns:
var a = 'hello';
doIfTrue(true, function() {a = 'world';});
console.log(a); // prints "world"
alert()
is one of very few exceptions to the no-blocking rule–nothing happens until the alert window closes. Even the timeouts freeze! This is one reason that it’s usually best to avoid using alert()
.
- Web Workers can allow you to run CPU-intensive tasks on a separate thread, but they’re structured very carefully to avoid race conditions. Beginning developers rarely need to worry about such things.
8. Automatic type conversions
Like many languages JavaScript does some automatic conversions between types under certain circumstances. For example:
var s = 1 + ""; // yields "1"
JavaScript takes this a little further than many languages, though, and you can leverage this fact to make your code very concise. For example, instead of
if(a != null) {...}
, you can just say
if(a) {...}
. Even though
a
isn’t a Boolean, JavaScript can tell you want to treat it like one, so it determines whether
a
’s value is “truthy” or “falsy” and acted accordingly.
Falsy JavaScript values include:
false
0
- empty strings (
''
or ""
)
null
undefined
NaN
Everything else is
Truthy.
Here’s another example:
var a = b || c;
If
b
has a truthy value like “Bob”, that’s the value that
a
will get. If
b
is falsy,
a
will get the value of
c
.
You can force a truthy/falsy value to become a true/false value by negating it twice:
var trueOrFalse = !!value;
Also, in equality comparison,
x == y
has some complex rules for converting different value types. This can yield weird behavior, like:
var a = "1" == true; // true: "1" -> 1, which is truthy
And you can use the
===
operator for strict comparison without automatic conversion.
var a = "1" === true; // false: different-typed values are not equal.
9. JavaScript is code–not data or markup
For simplicity, many online tutorials will mix JavaScript and HTML, like so:
Mixed HTML and JavaScript (bad)
<button onclick="return confirm('Delete this object?');">Delete</button>
This works fine for small tutorials, but it has some serious drawbacks when you’re writing real applications. It mixes the programmatic behavior of the UI elements (represented by JavaScript) into the
structure and
data that is (represented by HTML). HTML should not be deciding how it interacts with the JavaScript. Instead, separate your JavaScript code from the HTML, and let it decide how it interacts with the page.
HTML
<button data-confirmation="Delete this object?">Delete</button>
JavaScript (with jQuery)
// Set a click handler for anything with a data-confirmation attribute.
$('[data-confirmation]').click(function() {
var message = $(this).data('confirmation');
return confirm(message);
});
As you can see, the JavaScript has now defined the behavior for any element with a
confirmation
data attribute. This approach, sometimes called “unobtrusive JavaScript,” has several advantages, including:
- It usually makes HTML code more concise and readable.
- It allows you to declare a UI concept in one place and reuse it throughout your system.
- If you decide to change your implementation details (e.g. “browser confirmation dialogs are ugly and inconsistent–we want to use a popup instead!”), you can do this in one place without touching your HTML.
If you’re rendering templates in the browser
à la Angular or Polymer, this separation means putting HTML in one part of your file and JavaScript in another. If you’re using a server-side engine like JSP or Razor, you’re better off keeping the JavaScript code in static, cached
.js files, away from all your dynamically-generated HTML tags.
10. JavaScript is not just for browsers
JavaScript has clearly come a long way since it was created (
purportedly in ten days) in 1995. Now, technologies like node.js allow JavaScript to be run outside of any browser. Some common use cases for JavaScript as a general scripting language include:
- Writing server-side code in JavaScript. Ghost.org is just one example of a web application whose server-side code is JavaScript running on node.js.
- Building LESS files into CSS. Less.js is the fastest, most accurate LESS converter around, and it’s written in JavaScript. If you use LESS, but don’t want your LESS to get re-translated client-side on every page load, it’s a good idea to pre-build the CSS by invoking the lessc compiler via node.js.
- Managing build tasks. What if you want to run unit tests against your JavaScript every time you change a file, to make sure you didn’t break anything? Grunt.js, with its watch and Karma plugins can do that! Or you can configure Grunt to rebuild all those CSS files every time you change a LESS file.
If you’re developing web applications, you’re going to need to learn JavaScript. You might as well apply that knowledge to other aspects of your application too!
Conclusion
Like it or hate it–JavaScript is here to stay. It is currently the de-facto language for building any kind of rich user experience on the web.
JavaScript is powerful, and full of intricacies and nuances. The more you learn about JavaScript, the more you realize how little you actually know about it. But arming yourself with knowledge will help you avoid costly mistakes that are common for beginning developers.
Good luck.
About the Author
I’m a Software Architect with over 7 years of experience in building dynamic web applications. I have a passion for learning, and for helping other people learn. While earning over 60,000 reputation points on StackOverflow, I’ve seen a lot of the simple mistakes that vex JavaScript developers on a regular basis. If you have questions about JavaScript, or programming generally, feel free to reach out to me at
https://www.codementor.io/j2jensen
Special thanks to Aaron Frost and the peer reviewers at CodeMentor for their valuable feedback as I crafted this article.