JavaScript Variables, Scope, Hoisting, and Functions

Posted by rhhaynes on July 17, 2018

Variables in JavaScript (JS), like most programming languages, are storage containers that hold information for future referencing and/or modification. Such information can be any JS data type like numbers, strings, objects, etc. Given the significance of variables, this blog post serves to review several basic concepts including declarations, initializations, scope, and hoisting. These concepts are then discussed in regard to JS functions.

Declarations and initializations

To create or declare variables in JS, the var, const, and let keywords are used in conjunction with variable names. Ignoring keyword differences for now, the syntax of a simple variable declaration using var is shown below.

var cityName;
console.log(cityName); // undefined

In addition to specifying one of the aforementioned keywords, basic rules for declaring and naming variables include

  • variable names may only consist of letters, numbers, the dollar symbol ( $ ), and the underscore symbol ( _ )
  • variable names may not begin with numbers
  • variable names may not contain spaces
  • variable names may not be reserved JS keywords.

Although declarations bring variables into existence, their default values are undefined until assignments are made. For this reason and because it provides insight into their intended use and data type, initializing variables when they are declared is considered good practice. Revisiting the previous example, the syntax for a joint variable declaration and initialization is shown below.

var cityName = "Atlanta";
console.log(cityName); // Atlanta

Lastly it should be noted that there are important differences between the three declaration keywords based on scope, hoisting, and reassignment. These differences are presented in the table below and are discussed in the subsequent sections.

Keyword Scope Hoisting Reassignable
var Functional Yes Yes
const Block No No
let Block No Yes

Scope

Scope refers to the context in which a variable exists and thus can be accessed. The two types of scope in JS are global and local, where global refers to variables declared outside of all functions and blocks as well as variables initialized without one of the keywords. Because global variables can be accessed by all scripts on a page, using local variables is the preferred option to safeguard against overwriting variable values.

Unlike global variables, locally-scoped variables are only accessible inside the functions or blocks where they are defined. While all variables declared with a keyword are function-scoped (meaning they recognize functions as having separate scope), variables declared using const and let are also block-scoped. For block-scoped variables a new local scope is created by any code block (not just functions) including if and switch statements as well as for and while loops.

To illustrate the difference between function- and block-scoped variables, an attempt is made to initialize new variables inside the if statement below using var and let. Because let exhibits block scope, a new local variable with the same name as the original variable is created without impacting the original value. However because var only has function scope, it does not recognize the if block as having a new local scope. Consequently instead of creating a new local variable, the original var variable is reassigned in the same scope.

(function(){
  //local, function-scope initializations
  var cityNameVar = "Atlanta";
  let cityNameLet = "Atlanta";

  if (true){
    // local, block-scope initializations
    var cityNameVar = "Chicago";
    let cityNameLet = "Chicago";
		
    console.log(cityNameVar); // Chicago
    console.log(cityNameLet); // Chicago
  }

  console.log(cityNameVar); // Chicago
  console.log(cityNameLet); // Atlanta
}());

Lastly it should be noted that const behaves in a similar way to let, however variables created with const cannot be reassigned. As such const variables must always be initialized when they are declared.

Hoisting

Hoisting refers to the automatic shifting of var declarations to the top their scope such that variables can seemingly be used before they are declared. To illustrate this behavior, the example below provides a JS interpretation of code in which a variable is used prior to its declaration and initialization.

console.log(cityName); // undefined
var cityName = "Atlanta";
console.log(cityName); // Atlanta
// JS interpretation

var cityName;
console.log(cityName); // undefined
cityName = "Atlanta";
console.log(cityName); // Atlanta

As is evident, only the declaration is hoisted, not the initialization. Consequently the variable’s value is undefined until the actual assignment is reached. Nevertheless its usage does not throw a ReferenceError.

Failing to take hoisting into account can lead to unexpected results as demonstrated in the following example. Because the if statement evaluates to false, one might expect the console.log() output to be “Atlanta.” However since the var cityName declaration is hoisted to the top of the function, and because the assignment inside the if statement never executes, a value of undefined is actually output.

var cityName = "Atlanta";

(function(){
  if (false){
    var cityName = "Chicago";
  }
  console.log(cityName); // undefined
}());

Because of hoisting, it is considered best practice to always declare variables at the top of their respective scopes.

Functions

Like variables declared with var, JS function declarations begin with a keyword ( function ) and experience hoisting. The following example illustrates both a simple declaration as well as the ability to invoke a function prior to its declaration (hoisting).

console.log(cityName()); // Atlanta

function cityName(){
  return "Atlanta";
}

In much the same way that variable declarations are hoisted but their initializations are not, function expressions are not hoisted. Function expressions refer to the JS syntax whereby either a named function declaration or an anonymous function is stored in a variable. In the example below, a function expression based on an anonymous function is invoked prior to its definition.

console.log(cityName()); // TypeError: cityName is not a function

var cityName = function(){
  return "Atlanta";
}

Because only the var cityName declaration is hoisted from the function expression, invoking the function prior to its definition results in a TypeError since the value of cityName at this point is undefined.

It’s worth mentioning that a shorthand version of the function expression, known as the arrow function, also exists and is shown below. While there are other differences, the major advantages offered by arrow functions are a shorter syntax and the non-binding of this. Until arrow functions every new function defined its own this value, however arrow functions simply inherit the value from their enclosing context or function.

console.log(cityName()); // TypeError - cityName is not a function

var cityName = () => "Atlanta";