JavaScript - Variables
This article is a part of the Beginning JavaScript series.
Declaring Variables #
Declaring variables requires, at a minimum, two parts: a keyword, which indicates a variable declaration statement (except when you don't provide a keyword, see below), and an identifier - also referred to as a name - for the variable. JavaScript variables may be declared using one of three available keywords - var, let, and const. Given these three methods for declaring variables, what are the differences between them, and when should one be used over the other two?
While var
has been used since the beginning of the language, both let
and const
were only implemented in ES6 (2015). Before to ES6, let
and const
were considered future reserved words. The following sections will provide a little more information on the differences between the three keywords.
Note: Because JavaScript is a loosely-typed and dynamic language, variables can be assigned values of any data type.
var #
The original variable declaration keyword in JavaScript is var
. Variables declared with var
:
- can be reassigned as necessary
- they create properties on the global object in the default scope
- they are hoisted to the scope of the function they are declared in, the script's global scope
The var
keyword allows for the flexible declaration of variables, but perhaps it's a little too flexible. For example, the following snippet demonstrates using the keyword var
to declare a variable named someVariable
:
var someVariable; // someVariable declared and initialized to undefined
someVariable = 'some value'; // someVariable reassigned to 'some value'
someVariabble = 'another value'; // someVariable reassigend to 'another value'
Suppose a value is not provided at the time the variable is declared. In that case, the variable will be initialized with the value undefined. You can provide your user-defined value for initialization too:
var someVariable = 'some value'; // someVariable initialized to 'some value'
As stated earlier, these variables will be hoisted. While hoisting can be viewed as beneficial for function declarations, the hoisting of variables can lead to some "gotchas" if you're not careful.
Note: hoisting, as it is applied to functions, is what grants you the ability to declare functions either before or after you plan on calling them, which allows you more freedom in how you structure your code.
Not understanding how hoisting affects var declared variables can lead to what may seem to be strange side-effects. Still, the variables behave as intended. The most common mistake made, often perpetuated by those coming to JavaScript from other languages, is treating var
declared variables as though they were block-scoped. The following snippet demonstrates this issue:
for (var counter = 0; counter < 10; counter += 1) {
// loop body
}
console.log(counter); // undefined variable or 10?
As the comment indicates, one might believe that accessing the counter
outside of the loop would result in an error, but that is not the case; the console will show the value 10. Using var
to declare the variable will hoist it to the top of the global scope, and it will be available outside the loop. If you defined another variable named counter
in the global scope, the declaration in the for loop would have no effect. You would be utilizing the same globally-scoped variable in the loop. This behaviour runs counterintuitive to what most developers have learned from other languages.
A variable can only be declared once using var
, and redeclaring a variable with the same name in the same scope will have no effect. The result is that the variable identifier is hoisted, and the value is assigned (var
does not perform initialization). The following snippet demonstrates what will happen if a variable is redeclared using var
:
var num = 10; // num initialized to 10
var num; // this statement has no effect
console.log(); // 10
Without var #
It is possible to declare a variable without providing a keyword. The caveat here is that the variable must also be initialized, or else it will result in a reference error.
someVariable; // reference error
someVariable = 'some value'; // someVariable initialized to 'some value'
Initializing variables in this way will help ensure that you don't access them before initialization. However, the variables will still exist only in the function or global scope they have been declared in (the same effect as having been declared with var
). Initializing variables in this way is frowned upon, don't do it!
let #
Variables declared with let
behave similarly to those declared with var
; but there are some significant differences. For example, variables declared with let
:
- do not create a property on the global object
- they support block-scoping
- they cannot be accessed until they are fully initialized (unlike
var
,let
does perform initialization)
let someVariable; // someVariable initialized to undefined
let someVariable; // error, you cannot redeclare a variable using let
Variables declared with let
are still hoisted in the same way as var
variables, but that hoisting now includes defined blocks. The following is the same loop example from above, only with the counter
initialized using let
. Notice that now we cannot access the counter
outside of the loop body.
for (let counter = 0; counter < 10; counter += 1) {
// loop body
}
console.log(counter); // error, counter is not defined
So variables can be block-scoped, but they are still hoisted. What's most important when declaring and accessing variables is not the order in which the variable references appear but rather the order of execution and access to the variable. We have a function that accesses the externally declared variable num
below to demonstrate that hoisting is still taking place. Though the function is declared and references num
before its initialization, no error is raised because the function does not execute until after the variable initialization.
function fn() {
console.log(num); // num is referenced in code before initializaion
}
let num = 10;
fn(); // 10
const #
Finally, we have const
. Variables declared with const
abide by all the same rules as variables declared with let
, except that they must be initialized and cannot be reassigned. To be clear, while it may seem as though variables declared by const
are immutable, that is not the case. It is simply impossible to reassign new value to a variable declared by const
. So, if you initialize a const
variable with a string, that variable will only ever return its initial value. Likewise, suppose you initialize a const
variable with an object. In that case, you may still mutate the object, but you won't be able to reassign a new object to the variable.
const NUM; // error, missing initialization
const NUM = 10; // initialized to 10
NUM = 100; // error, invalid assignment
const obj = { foo: 'foo' }; // initialized to object
obj = {}; // error, invalid assignment
obj.bar = 'bar'; // valid object mutation
console.log(obj); // { foo: 'foo', bar: 'bar' }
Summary #
Given the various ways to declare variables, you may be wondering, "Which method should I use?" While there is no right or wrong answer to this question, I think we can prefer certain methods over others. First, we can make the case to avoid using var
when declaring variables. Both let
and const
supersede var
as each of the former keywords provides intuitive scoping behaviour and won't pollute the global namespace in the same cases as var
. When it comes to choosing between let
and const
, likely many, or even most, variables you declare will never have to be reassigned. Therefore, declaring variables using const
would be the best choice to prevent you from performing a reassignment in error. Only declare variables you will use for repeated assignments with let
. Of course, the previous statements are merely suggestions. Take a look at whatever JavaScript style guide you're adhering to for direction on variable declaration and naming conventions.
The TLDR for variable declaration is simple: prefer const
over let
, and prefer let
over var
.