First published July 28, 2019 in ITNext

Var, Let & Const: JavaScript ES6 Feature Series (Pt 1)

Let's begin at the beginning...

Close up of children's blocks

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.

Barney head exploding

Sorry, couldn’t help myself — I love Barney from How I Met Your Mother.

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 vars 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.