frontend
Var, Let & Const: JavaScript ES6 Feature Series (Pt 1)
Let's begin at the beginning...
Introduction
The inspiration behind these posts is simple: there are still plenty of developers for whom JavaScript is a mystery — or at the very least, a little less understood.
Add to that ES6, and the updates that the ECMAScript committee is now planning to release yearly, and it’s a lot of info to keep abreast of.
I wanted to provide a bunch of posts about JavaScript ES6 features that I use regularly, for developers to reference.
The aim is for these articles to be short, in-depth explanations about various improvements to the language (and how to use them), that I hope will inspire you to write some really cool stuff using JS. Who knows, you might even learn something new along the way. 😄
For my first post, it makes sense to begin at the beginning with var, let, and const: the building blocks of JavaScript.
Var
var
is the original JavaScript syntax: the OG way to declare a variable, and optionally, initialize it as a value. It’s been around since the beginning of the language, long before ES6 was even a thought in anyone’s head.
Variables have no pre-defined or attached type in JavaScript, meaning any value can be stored in any variable. Some examples:
Assigning values to variables
var one = 1; // the integer 1 has just been assigned to the variable one
var two = 'two'; // the string 'two' has been assigned to the variable two
var paige = {
gender: female,
hairColor: red,
eyeColor: blue
};
// the object containing a person's traits has been assigned to the variable paige
All of the var
s above are valid examples of how JavaScript allows any value to be assigned to any variable, unlike strongly typed languages like Java, which (until recently), required all variables be declared and typed before compile time.
Another thing to know about var
is that it is regardless of if it is globally or locally scoped, these variables can be re-declared and updated at will. Check out this perfectly valid example.
Reusing the same variable with different value types
var greet = "Hello there";
console.log(greet); // prints: "Hello there"
greet = true
console.log(greet); // prints: true
greet = 11;
console.log(greet); // prints: 11
greet = welcome = (name) => {
return `Hi ${name}. Welcome!`
};
console.log(greet("Paige")); // prints "Hi Paige. Welcome!"
Although I reused the existing variable greet
, JavaScript throws no error, regardless of whether the variable is a string, a boolean, an integer — heck, even a function.
Variable declarations, lexical scoping, and hoisting
There are a few things to be aware of (which have tripped up many a developer, myself included), when using var
. These types of variables are "lexically scoped" and subject to what’s known as "hoisting."
What this means in execution is that a var
-type variable, no matter where it is defined inside of a block of code, will be pulled to the top of its scope before code execution. For example, if you wrote a few lines of JavaScript, then declared var tree = "elm";
, at run time, behind the scenes, var tree would be "hoisted" to the top of the scope where it was defined.
It may not be assigned to the value of "elm"
until several lines later, but the variable would exist according to the JavaScript engine. Here’s an example to show how this works, and what it could mean.
Variable declaration versus variable assignment in JavaScript
var greeter; {/* the JS engine knows greeter is a variable,
but it doesn't know what value greeter has */}
console.log(greeter); // currently, greeter prints: undefined
greeter = "say hello"; // now, greeter's been assigned to the value "say hello"
console.log(greeter); // prints: "say hello"
Lexical scoping also comes into play here. Without going too much into the details of lexical scoping in JavaScript (because that’s a whole other blog post), the word “lexical” refers to the fact that lexical scoping uses the location where a variable is declared within the source code to determine where that variable is available.
This boils down to if a variable is created in the global scope, it’s hoisted to the top of the global scope, if the variable is created within a function, it’s hoisted to the top of the function, and so on and so forth.
Here are examples of variable hoisting and lexical scoping in action as the JavaScript engine runs.
Variable hoisting based on lexical scoping
var something; // declaration is hoisted to the top of the global scope
console.log(something); // prints: undefined
function doAnotherThing() {
var anotherThing; {/*} declaration is hoisted to the top of the function's
lexical scope but not immediately assigned a value */}
var thisOneThing = "a defined value"; // this variable is hoisted and then immediately defined
function useAnotherThing(anotherThing) {
console.log(`The value of another thing: ${anotherThing}`);
}
function useThisOneThing(thisOneThing) {
console.log(`The value of this one thing: ${thisOneThing}`)
}
useAnotherThing(anotherThing);
{/* prints: "The value of another thing: undefined" because the variable
wasn't assigned a value until AFTER the function useAnotherThing() ran */}
anotherThing = "an undefined value";
// finally the variable, initialized earlier, is assigned a value
useThisOneThing(thisOneThing);
{/*} prints: "The value of this one thing: a defined value" because
the variable was assigned a value before the function useThisOneThing() ran */}
}
something = "that thing";
console.log(something); // prints: "that thing"
console.log(doAnotherThing()); // see printouts above next to inner functions
In the example above I show how the variables something
and anotherThing
, by not being immediately assigned values after being declared are undefined
when console.log()
is called on them. Once their values are set further down the script and console.log()
is called again, they print their values to the console.
The variable thisOneThing
, however, is initialized and assigned a value immediately after initialization, meaning that when the function using it: useThisOneThing()
is run (which is also an example of a JavaScript "closures" at work), it prints out the string "The value of this one thing: a defined value."
I think this is enough about var
and how it works. There’s a million other tutorials talking about it, too. Now, let’s get on to the exciting, new ES6 variable syntax, and how these types of variables are different.
Let
let
was finalized and officially introduced for widespread use with the release of ES6 in 2015. Of the two new variable types introduced, it is the much more flexible and ephemeral of the pair.
Let's similarities to var
While let is a totally new variable type, it does have share a couple of commonalities with var
. For instance:
- When
let
is initialized, it can optionally be assigned a value at initialization time, let
is also a variable type that can be mutated and / or reassigned to different data types at run-time.
And that’s pretty much where the similarities end.
Let's differences to var
Ok, here’s where it gets interesting and let
begins to differ from var
.
Block scoping and variable declaration and assignment
let
is block scoped to the statement, or expression within which it is used, unlike var
, which defines a variable globally, or locally to an entire function regardless of block scope.
Bottom line: let
lives inside a chunk of code (usually) defined by curly braces like so {}
.
Additionally, let
’s value can be updated but not redeclared as a new variable inside of the same scope, which var
-type variables can. There’s an example below showing the differences between let
and var
.
Valid assignments of let based on block scoping
Here is a valid example of assigning the variable greeting using the let keyword in the same file, but in different block scopes.
let greeting = "say Hi"; // one variable declared in the global scope
if (true) {
let greeting = "say Hello instead";
{/* another variable of the same name declared within this inner, block scope */}
console.log(greeting); // prints "say Hello instead"
}
console.log(greeting); // prints: "say Hi"
Invalid assignments of let due to block scoping
Since this let greeting
variable is assigned a value in the global scope, it cannot be redeclared and reassigned to a new value within the same scope. If this scenario occurs, it throws a SyntaxError
in the console.
let greeting = "say Hi";
let greeting = "say Hello instead"; //SyntaxError: Identifier "greeting" has already been declared
Doing the same type of thing with var produces no such error.
var fruit = "banana";
console.log(fruit); // prints: "banana"
var fruit = "pear";
console.log(fruit); // prints: "pear"
Variable hoisting (or lack thereof) and global objects
Hoisting does not apply to let
. Unlike var
a let
-type variable won’t be initialized to undefined
before its definition gets evaluated by the parser.
If you try to use a let
variable before the part of the code where it’s initialized, you’ll get a ReferenceError
thrown.
ReferenceError of let because it was called before parsed
console.log("test 0", test)
let test; // prints: ReferenceError: test is not defined
And at the top level of programs and functions, let
, unlike var
, does not create a property on the global window
object when declared in the top-most scope either. It’s not necessarily a bad thing, it means less pollution of the global namespace, which we can all benefit from, but it also means you can’t use the oft misunderstood this
property to access said global variables either.
No global object properties with let
var x = "global";
let y = "global";
console.log(this.x); // prints: "global"
console.log(this.y); // prints: undefined
Ok, I think that’s enough about let
, it’s time to move to the stricter, more immutable variable type const
.
Const
const came about in the same ES6 release as let
back in 2015. But where let
provides increased flexibility and less permanent variables, const
provides more rigidity and permanence.
It is also, by far, the most different of the three variable types.
Const's defining traits
This variable declaration shares some things with let.
const
, too, is limited to block scoping,- It does not create properties on the
window
object when it’s created in the global scope, - When it is declared it must also have a value assigned to it,
- And it is not hoisted at run time.
Block Scoping
Const has the same type of block scoping that let
does, but once const
is used to declare a variable, that value can’t be changed through reassignment or redeclared within that scope.
const reassignment TypeErrors and redeclaration SyntaxErrors within the same scope
const pet = "dog";
console.log(pet); // prints: "dog"
pet = "cat"; // TypeError: Assignment to constant variable
console.log(pet);
const person = "Sean";
const person = "George"; // SyntaxError: Identifier "person" has already been declared
Both pointing the existing pet
variable towards a different value or trying to redeclare person
within the same scope results in errors in the console.
But with block scoping in mind, the following examples work with no issues.
Valid block scoping of variables using const
const pet = "dog";
console.log(pet); // prints: "dog"
if (true) {
const pet = "cat"; // TypeError: Assignment to constant variable
console.log(pet); // prints: "cat"
}
const person = "Sean";
console.log(person); // prints: "Sean"
function meet() {
const person = "George";
console.log(`Meet ${person}`);
}
console.log(meet()); // prints: "Meet George"
Immutability (sort of)
const
creates a read-only reference to a value. The caveat to that statement though is the value itself is mutable, but const
, once assigned, cannot be reassigned.
If const
is assigned to an object, the properties within that object can still be updated and changed.
const’s reference to an updatable object
const greeting = {
message: "Hello there",
person: "Paige"
};
console.log(greeting); // prints: { message: "Hello there", person: "Paige" }
greeting.message = "Hi there";
console.log(greeting); // prints: { message: "Hi there", person: "Paige" }
Simultaneous declaration, initialization and no global object
Global constants like const
do not become properties of the window
object, unlike var
variables. An initializer for a constant is also required; that is, you must specify its value in the same statement in which it's declared (which makes sense, given that it can't be changed later).
const not polluting the global scope with object properties
var x = "global";
const y = "global";
console.log(this.x); // prints: "global"
console.log(this.y); // prints: undefined
If const
is not assigned a value when it is initialized it will throw a SyntaxError
. Likewise, if a script tries to invoke it before the parser has reached the variable, it will throw a ReferenceError just like let
because const
is not subject to hoisting and it cannot be set to undefined
before being assigned a value.
Errors From calling variables before declaration or initializing with value assignment
console.log(fruitTree); // ReferenceError: fruitTree is not defined
const fruitTree = "fig tree";
const tree; // SyntaxError: Missing initializer in const declaration
And that is about all there is to know about const
, as well.
Conclusion
The JavaScript language has been around for more than 20 years and even ES6 has been out since 2015, but even so, there’s still plenty of misconceptions and knowledge gaps about it.
My aim of this series is to dispel those myths and give you a better understanding of JavaScript, and introduce some of the ES6 syntax you may use everyday but not fully grasp the nuances of.
Since everything in JavaScript is built around variables, it seemed like an appropriate place to start with var
, let
, and const
.
Check back in a few weeks, I’ll be writing about JavaScript, ES6 or something else related to web development.
Thanks for reading, I hope you learned something new about the most basic building blocks of JS: the humble variables. Please share this with your friends if you found it helpful!
References & Further Resources
Want to be notified first when I publish new content? Subscribe to my newsletter.