Preface
When learning and working in front-end web development tools nowadays (like React), it's quite common to see developers learn to use features of the JavaScript language or frameworks like React without fully understanding what is going on. This is okay in the short-term as it allows individuals to quickly apply the things they are learning and "see the results of their work".
However, in the long-term, and as you start to encounter more challenging problems in your work, I find that it is beneficial to take a moment every so often to deep-dive into the fundamentals of the technologies we are using on a daily basis. That is exactly what I hope to do in this blog post.
Variables in JavaScript
Variables are the backbone of any programming language, and variables in JavaScript are no exception. If you want to write any sort of interesting program in JS, you'll need to use variables in some shape or form.
When we are first learning JavaScript, the canonical way we learn to define a variable is something like this:
var name = "Treasured";
Let's break this down:
var name
→ This part of the statement tells the JS interpreter that we are declaring a variable named name=
→ The equals sign here is an assignment operation. We are taking the value of the expression to the right of this equals sign and assigning it to the variable referenced on the left"Treasured"
→ This is string literal value containing the word "Treasured";
→ A semi-colon delimits the end of this statement (Note: In modern JS, the semi-colon is often optional as most JS engines can determine when a statement ends using whitespace and the rules of the language itself. I've included it here just for consistency sake since we enforce its use in our code-base)
If you were to read this statement in the English language, you might say this line "declares a variable called name AND assigns its value to be the string Treasured". The key thing to notice here is that there are actually 2 separate operations being performed in one line. We can see this clearly if we re-write the statement in the following equivalent form:
var name; // Declare a variable called namename = "Treasured"; // Assign it's value to the string "Treasured"
In this snippet of code, we've broken down the original statement into its simplest parts:
- Variable declaration
- Variable assignment
This process is pretty much what JS is doing when it sees the first line of code. JavaScript makes a clear distinction between variable declaration and assignment.
Now, you may be asking, "Okay Umar, so why does this matter? What difference does it make writing the declaration and assignment separately like this?"
Okay, okay, you're right, it doesn't seem like there is any difference between these two versions of the same statement, but I wanted to show you that the way JavaScript deals with variables is a little different from what you might have seen in other languages like Java and C#.
This'll become super apparent when you consider the following function:
function doSomething() {age = 2;var age;return age;}
If you were to call this function in a language like Java, it would raise an error. Similarly, based on how much I belaboured the point about JS separating variable declaration from the assignment, you might also think that line 2 of this function would raise an error when this function is called since age
is assigned a value before it has been declared.
However, if you run this code in your browser's console, you get a surprising result:
> doSomething()2
HUH?!!?!
What's going on here?
Well, there's an interesting language feature of JavaScript that is at play here and it has very much to do with the fact that we've put this assignment statement inside the body of a function and the point I made earlier about variable declaration being separate from assignment in JS.
Let's take a closer look in the next section...
Function-scope
In JavaScript, variables declared using the var
keyword have what's called "function scope". This means that they can be accessed anywhere in the function they are declared in, including nested blocks like loops, if-statements, etc.
For example,
function f() {if (true) {var a = 3;console.log(a); // a = 3}console.log(a); // a = 3}
Here the variable a
is still in scope on line 7 since it is within the function body and a
has function scope.
Under the hood
In JavaScript, all var
variable and function declarations are "hoisted" up to the top of their function scope, while the variable assignments stay in place.
Going back to the example above, what ends up happening is that before the function is run, all the variable declarations are hoisted to the top of the function, like so:
function f() {var a; // Variable declaration hoisted to topif (true) {a = 3; // Variable assignment stays in placeconsole.log(a);}console.log(a);}
So in this case, a
is in scope throughout the body of the code due to function scoping rules.
Unexpected Issues
What happens in the following snippet of code:
function doSomething() {age = 2;return age;}console.log(doSomething());console.log(age);
Using your understanding of function scoping, you might say that this will log something like this to the console:
2Uncaught ReferenceError: age is not definedat <anonymous>:1:13
since age is not defined outside of the doSomething
function.
However, when you actually run this you get:
22
So what's going on? How is age
defined outside the function when we just said that variables defined using var
are function-scoped?
The answer once again comes down to hoisting...
When you assign a variable without first declaring it like I did above, there is no declaration to hoist, and so the variable gets hoisted to the global scope. At this point, it is now available to every function, and it becomes hard to tell what is in scope.
You can help prevent this issue by adding:
"use strict";
to the top of your .js
file.
This will tell the JS engine to raise an error whenever it encounters a variable assignment without an appropriate declaration:
"use strict";a = 3; // Will raise an error instead of hoisting
Avoiding Function-scope
The semantics of function-scoped variables and hoisting are often very tricky to reason about. Coming from other languages, this concept seems very foreign and confusing. For this reason, in ES6 the let
and const
keywords were created.
let
and const
abide by what is called block scope, which means that only the current "block" of code can access them. Let's see them in action:
function g() {let a = 3; // Block scope, accessible anywhere inside functonif (true) {console.log(a);}console.log(a);}
And here's a more interesting example:
function g() {if (true) {let a = 3; // Block scope, accessible inside if-blockconsole.log(a);}console.log(a); // Error, a is not in scope}
So let
allows us to declare variables without "leaking" scope when not required. What about const
then?
Well const
is very similar to let except variables assigned using const
cannot be re-assigned. Here's an example:
const a = 3;a = 2; // Throws an error, cannot re-assign a
Best Practices
- Where possible, try to use
const
instead oflet
- Avoid using
var
if possible to avoid confusing function-scoping semantics, but understand how to use it if need be
Conclusion
Hopefully, by the end of this article, you have gained a greater appreciation for how variables work in JavaScript and some of the pitfalls of using the var
keyword to assign variables in modern JavaScript.
If you have any questions or comments, feel free to message me on Slack or leave a comment on this post!