Variables and Constants

What are variables and constants?


let sum = 0;
let firstAddend = +prompt('Fill in the first number to be added here:');
let secondAddend = +prompt('Fill in the second number to be added here:');
sum = firstAddend + secondAddend;
alert(firstAddend + ' + ' + secondAddend + ' = ' + sum);

In the above example sum, firstAddend and secondAddend are variables. A variable or a constant is a named reference to a particular data value. You can consider them as a container that you give a name and where you can store some value in. The name must be unique and cannot be changed after declaration, but the value can be changed as often as you want, that is, when it is a variable. Constants are given a fixed value that cannot be changed.

While programming, developers often do not know what a value will be during execution of the program, so they use the variable or constant name as a reference to the unknown value instead. Once assigned a value, a variable or constant can be used over and over again in the program without the need to assign or calculate its value over and over again. This makes the use of variables and constants a fundamental part of programming in any programming language.

Naming a variable or constant is called declaration, generally in conjunction with a keyword var, let or const. Assigning a variable or constant its initial value is called initialization.

Declaring variables and constants

Before being able to use a variable or constant it first needs to be declared using a statement. For declaring a variable, keywords let or var are used. A constant is declared by using the keyword const. The declaring keyword is followed by a unique name, an identifier, you give to the variable or constant: let someVariableName; or const someConstantName;. Identifiers are case-sensitive and valid identifiers are only allowed to contain letters, $, _, and digits (0-9), but may not start with a digit.

Before the introduction of ECMAScript 6 (2015) only the var keyword existed to declare variables and there was no distinction between constants and variables. The var keyword can still be used, but in new projects it is recommended to use the let and const keywords instead.

Redeclarations, i.e. using the same identifier in different declarations, in conjunction with let or const keywords, throw a SyntaxError. Redeclaring a var variable using the var keyword merely acts as an assignment of a value to the same variable. Mind that this all only applies to (re)declarations within the same scope, as explained in more detail later.


let someVariable; // declaring variable "someVariable".
let someVariable = "someValue"; // throws a SyntaxError to the console: someVariable is assigned a value but ALSO redeclared by using "let".

const someConstant = "Jill";
let someConstant = "John"; // SyntaxError: redeclaration of someConstant

let someVariable;
someVariable = "someValue"; // no error: someVariable gets a value assigned.
var someVariable = 3.14; // SyntaxError: redeclaration of someVariable. 

var varVariable = 5;
var varVariable; // redeclaring without assigning a value does not change the value!
console.log(varVariable); // logs: 5
var varVariable = 3; // assigning a new value.
console.log(varVariable); // logs: 3

Declaring multiple variables or constants can be done in one statement. The declarations need to be separated by a comma.


const someConst1 = 2,
      someConst2 = "foe";
let x, y = 3, z = 0;	
This is equivalent to:

const someConst1 = 2;
const someConst2 = "foe";
let x;
let y = 3;
let z = 0;	

By informal convention variables and constants are named using camelCase (with initial lowercase letter). The name may not start with a digit. It is widely considered best practice to name JavaScript variables self-descriptive. It should not be necessary to add a comment to explain what a variable represents.


// this is bad practice:
const φ = (1 + Math.sqrt(5)) / 2; // This is the "golden ratio".
let a = 5;
let value1 = "Jane";

// this is good practice:
const goldenRatio = (1 + Math.sqrt(5)) / 2;
let interestPercentage = 5;	
let firstName = "Jane";

Assigning a value to variables and constants

A constant needs to be explicitly initialized, i.e. explicitly assigned an initial value, at declaration. The value of a constant cannot be changed once declared and initialized.


const twoPi; // this will throw a SyntaxError; no assignment at declaration of const twoPi.

const twoPi = 2 * Math.PI; // declaring const twoPi and assigning the value 2 times π.
twoPi = 6.2832;  // this will throw a TypeError; invalid reassignment to const twoPi.

Variables can optionally get explicitly assigned an initial value at declaration. If a variable is not explicitly initialized, it automatically, implicitly, gets initialized with the default value undefined. Subsequently the undefined variable can be explicitly assigned a value somewhere after declaration, in a separate statement. Variables can be unlimitedly reassigned a value after initialization.


let interestPercentage = 2; // variable explicitly initialized at declaration.
interestPercentage = 1.24; // reassigning a value to interestPercentage.
interestPercentage = 1.33; // reassigning a value to interestPercentage.

let firstName; // declaring a variable, without explicitly assigning an initial value.
console.log(firstName); logs: undefined
firstName = "Jane"; // assigning to firstName.
firstName = "John"; // reassigning to firstName.

In normal JS (not in strict mode) assigning a value to a non-declared variable automatically declares that variable. This only holds for variables. Constants always need to be declared using const.


// Although a keyword is absent, next statement both assigns a value AND declares the variable,
// but only in normal JS, NOT in strict mode.
areEqual = false;
console.log(areEqual); // logs: false

// In strict mode all variables need to be declared using "let" (or "var")
function someFunction() {
  'use strict';
  isLoggedIn = true; // this line will throw a ReferenceError; assignment to a not declared variable.
}
someFunction(); // calls (invokes) the function.

BTW: JavaScript functions will be described in a later chapter, but the basic principle is simple: a function is a block of code to perform a particular task. It can be executed an arbitrary number of times by invoking it somewhere else in the code.

The assigned value is of a certain type, like a number (e.g. 0.5 * Math.PI), a string (e.g. "John" or 'John'), or a boolean (true/false). Later more about data types.

Scope of a variable or constant

Constants and let-variables have global scope (scope of the entire script) or local block-scope, depending on where they are declared. A block is code between curly brackets {}, like for instance the body of a function.

Each variable (and constant) is only accessible in the scope in which it is declared, including all inner scopes nested within that scope. An inner scope has access to its outer scope, "top down", but not vice versa.

Declaring a variable (or constant) in a nested inner scope creates a new local variable for that inner scope and its child-scopes. If a variable (or constant) with the same identifier (same name) was already declared in a parent scope, it "overwrites" this variable with the new declaration.

Variables declared using var, so called var-variables, declare global scope or local function scope but not any other block-scope.


var x = 1;
let y = 2;

{ // here a block starts
  var x = 10; // assigning a new value to the global variable x
  let y = 20; // declaring a new local variable, 'overwriting' global variable y for this inner block scope
  console.log(x); // logs: 10 
  console.log(y); // logs: 20 			
} // here the block ends

console.log(x); // logs: 10 
console.log(y); // logs: 2 // local variable y = 20 is not available here in global, i.e. parent, scope.

var x = 10;
let y = 2;

function someFunction() {
  var x = 12; // declaring a new local variable, 'overwriting' global variable x for this inner function scope			
  let z = 1;
  let k = "Hello";
  {		
    let z = 2; // new variable, 'overwriting' outer function scope variable z for this inner block-scope 
    console.log(z); // logs: 2
    let y = 3; // new variable, 'overwriting' global variable y for this inner block-scope
    console.log(y); // logs: 3
    k = "goodbye"; // function scoped variable k gets reassigned a value. 
    console.log(k); // logs: 'goodbye'
  }
  console.log(x); // logs: 12
  console.log(y); // logs: 2  
  console.log(z); // logs: 1
  console.log(k); // logs: 'goodbye'
  
  y = 20; // global scoped variable y gets reassigned a value.
}

someFunction(); // calls (invokes) the function
                // logs all the values as shown in the function above
console.log(x); // logs: 10
console.log(y); // logs: 20
console.log(z); // ReferenceError: z is not defined (local variable z is not available here in global, i.e. parent, scope) 

Variables and constants are declared for their entire scope. In case you are wondering if declarations need to be at the top of their scope or if they can be anywhere in scope; this will be explained in the next sections Hoisting declarations and Temporal Dead Zone (TDZ).

As mentioned before, assigning a value to a variable in non-strict mode and the variable is not declared anywhere in scope by using a keyword (var or let), the JS engine assumes you want to create that variable and declares it automatically. In that case it always creates a global variable, an implicit global, even if the variable is declared in local scope, such as in a function. In strict mode it will throw a ReferenceError.


let x = 0; // declares x within global scope and explicitly initializes it value 0.

function myFunction() {
  let y = 2; // declares y within scope of myFunction and explicitly initializes it value 2.
  function mySubFunction() {
    x = 3; // reassigns 3 to existing global var x.
    y = 4; // reassigns 4 to existing local var y in scope of function myFunction, which includes mySubFunction.
    z = 5; // creates a new GLOBAL variable z, an implicit global (ReferenceError in strict mode).
  }
  mySubFunction(); // calls function mySubFunction (is necessary to invoke the function).
  console.log(x, y, z); // logs: 3 4 5, after calling myFunction
}

myFunction(); // calls function myFunction (including calling mySubFunction).
              // logs: 3 4 5
console.log(x, z); // logs: 3 5
console.log(y); // returns a ReferenceError, as y is local to function myFunction.

Hoisting declarations

In JavaScript declarations are processed before any code is executed. Consequently, when the executing of the code starts, the declarations are already processed and the variables and constants and their scopes are "known". Declarations introduced anywhere within their scope are, so to speak, hoisted to the top of their scope as it appears as if the declarations are moved to the top of their scope.

Therefore, declaring a var-variable anywhere in global or function scope is equivalent to declaring it at the top of its scope. So, a var-variable can be used before it's declared! Only declarations are hoisted, not assignments. If a var-variable is declared and assigned an initial value after using it, the value will be undefined. This is kind of strange, because getting the value undefined is the result of an implicit assignment, as explained before. It implies that hoisting a declaration also includes an implicit assignment of the value undefined. This is only the case with var-variables; let-variables do get hoisted, but they cannot be accessed until they are initialized, which always happens where the declaration statement is in the code. More about this in the next section Temporal Dead Zone.


// Hoisting var-variable declarations:

"use strict"; // next code is in strict mode!!!

inputValue = 2;
console.log(inputValue); // logs: 2
var inputValue; // This initial declaration is hoisted to the top of the script.

console.log(outputValue); // logs: undefined
var outputValue = 20; // the declaration is hoisted, but NOT the assignment.

var x = y, y = 3; // the declaration of y gets hoisting and initialized with value undefined at the time x = y is evaluated. 
console.log(x + y); // logs: undefined // x is undefined, y is 3.

var a = b = 3, b; // in non-strict mode also just "var a = b = 3;" will work: variable b will then be created as an implicit global. 
console.log(a * b); // logs: 9.  	

We know that in non-strict mode, when assigning a value to a variable, the JS engine automatically assumes an implicit global if the variable is not declared by using a keyword (var or let). But these implicit declarations are not processed before any code is executed and hence are not hoisted.


// Implicit globals are not hoisted:

console.log(myVar); // ReferenceError: myVar has not been defined yet. 
myVar = true; // assumes an implicit declaration, but is not hoisted. 

Temporal Dead Zone (TDZ)

Declarations using let and declarations using const are hoisted to the top of their scope as well, but under different conditions than with var declarations.


let x = 2;
function myFunction() {
  console.log(x); // throws a ReferenceError: can't access declaration 'x' before initialization.
  let x = 20;
}
myFunction();

y = 2; // throws a ReferenceError: can't access declaration 'y' before initialization.
let y;

In the example above console.log(x) throws a ReferenceError instead of undefined as would be the case with a var-declared variable. So, has this let-variable x been hoisted? If the declaration in let x = 20 would not have been hoisted to the top of myFunction()-scope, console.log(x) would have returned the global variable x = 2. So apparently let-variables are being hoisted. But why does console.log(x) throw a ReferenceError and not undefined, and why does console.log(y) throw a ReferenceError when let y gets hoisted?

Unlike var-variables, variables and constants declared with let and const cannot be accessed until they are initialized. Technically initializing always happens where the declaration statement is in the code. When no specific initial value is assigned to a let-variable, it is initialized with a value undefined, as mentioned before. And initializations are not hoisted. The let-variables and constants are hoisted, so they enter scope at the start of the containing block, but they get initialized where they are actually declared. The variable or constant is said to be in a "temporal dead zone" (TDZ), from the start of scope until the declaration statement has been fully evaluated at the actual location of the statement in the code. A variable or constant cannot be accessed (read/written) when it is in the TDZ. Accessing the variable within the TDZ results in a ReferenceError.


// variable y is hoisted to the beginning of scope: TDZ starts here.
y = 2; // throws a ReferenceError: can't access declaration 'y' before initialization.
let y; // here, after evaluation of the declaration the TDZ has ended.

// TDZ of myVar starts here at beginning of scope.
console.log(myVar); // access myVar within the TDZ throws 'ReferenceError'.
let myVar; // End of TDZ
myVar = 3;

// TDZ of myVar starts here at beginning of scope.
function myFunction() {
  console.log(myVar);
}
let myVar; // End of TDZ
console.log(myVar); // logs: undefined
myVar = 3;
myFunction(); // logs: 3 // because myFunction() is called outside TDZ.

In general, it is good and common practice to declare and initialize variables and constants at the top of the scope in which they are used.