With the rise of thick client-side applications, and Node.js, JavaScript has become an increasingly important programming language. Despite its simplicity, JavaScript presents some difficulties for those new to the language. I thought it would be useful to outline several JavaScript errors that I commonly made when I was learning the language.
Scope: this, that and window
Like many programming languages JavaScript provides internal access to objects using the keyword this
. Unfortunately, what this
refers to differs depending on what called the function containing this
. A common example, shown below, is what happens when setTimeout
is called.
In the example below a Dog object is created, with a bark function. The Dog ralph is instantiated with the name ‘Ralph’ and when ralph.bark()
is called, “Ralph” is printed to the console.
What becomes confusing is what happens when setTimeout
is called with the parameters ralph.bark
and 500. After 500 milliseconds ralph.bark
is called, however, nothing is printed to the console.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
Mozilla Developer Network refers to the problem above as ‘The ”this
” problem’. What happens is this
within bark()
refers to the browser’s window
variable when bark()
is called from setTimeout.
Avoiding the this
problem.
1 2 3 4 5 6 7 8 9 10 11 |
|
In each of the above the bind, or proxy functions explicity ensure that this
within ralph.bark
refers to ralph and not window
. In the final example an anonymous function is called and provides another way of fixing the this
problem.
Callbacks in Loops
When I launched Understoodit.com in May I included a waitlist for interested users to signup. I was planning on inviting a few dozen users a day, however, due to a deluge of emails sending out invites was delayed by a week or two.
To send the invites out, I went to Understoodit’s admin panel and seleted 40 people on the waiting list and clicked invite. A few days later I noticed that only a few individuals had accepted the invite. I looked at Postmark’s logs and noticed that invites were only sent to 5 individuals. What’s more those 5 individuals had received anywhere from 5 - 15 emails each. Meanwhile, the other 35 invitees had received no emails. The bug: I had a callback in a loop that iterated over all the selected invities and 1) called databaseModule.addInvitedUser, which created an invite token and added that invite to the database and 2) sent an email with the newly created token. Below is a simplification of the code, with error handling removed.
1 2 3 4 5 6 7 8 9 10 |
|
What went wrong was that the anonymous function “captures the variable i, not its value”. The value of i
is dependent on when the anonymous function is called, which varries depending on how long it takes to add the invited user to the database.
The solution I used was to wrap the anonymous function with an immediately invoked function expression (IIFE). The IIFE “locks” in the value of i
ensuring that emailModule.sendInvite()
refers to a different value of i on each IIFE call. Alternatively, one could create a second function outside of the loop and then call that (see option 2), a solution that would likely be easier to read.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
|
The above quirk seems similar to a fairly common JavaScript interview question that takes the form of adding event listeners to an array of links.
Global Variables
The problems with global variables have been discussed many times before. Suffice it to say that you should avoid them by using var
(e.g. var x = 0;
vs x = 0;) when first declearing a variable. If you have a variable that has unaccounted for properties or values, there’s a chance that a global variable could be to blame.
I’d highly recommend defining all your variables at the top of the function to make it as clear as possible when a var is missing. Furthermore I’d recommend using JSHint which can warn you of global variables.
Values in HTML forms
1 2 3 4 5 |
|
When it comes time to get the form’s values, and use them within an application, I usually do something like:
1 2 3 4 5 |
|
Unfortunately the value that jQuery returns is a String
, and comparing it to 1, a Number
returns false. Here are several solutions to this:
1 2 3 4 5 6 7 8 9 10 |
|
In the first two examples orderSize is explicitly converted to a Number
using the Number
constructor and the global parseInt
function. In the third example the double equals coerces orderSize to a Number
before comparing to 1 (MDN on comparison operators). However, I’d recommend going with the last approach, which allows you to use orderSize as a Number
in multiple spots without having to repeatedly caste to a number. If you don’t like the last approach I’d recommend the first or second approach, since it seems to be generally preferred to use the strict equal (tripple equal signs) and not to use the double equal sign.
Conclusions
Many of the above errors can be avoided by following JavaScript style guides (Google JavaScript Style Guide, Addy Osmani on Style Guides). A sign of how tricky JavaScript is comes from Github’s JavaScript Style guide that goes as far to recommend avoiding JavaScript altogether and using CoffeeScript - a suggestion a few would disagree with!