Hoisting, Shadowing, Closures

January 15th, 2016 - Bonn

Notes about some mysterious sounding terms in Javascript.

Hoisting

This refers to how Javascript internally moves all declarations to the top of the current scope (imagine a crane hoisting = lifting them up to the top). That scope could be the whole script or the function they are in, blocks like if/for don’t define their own scope.

So

function foo() {
  bar();
  var x = 1;
}

is actually

function foo() {
  var x;
  bar();
  x = 1;
}

but

if (...) {
  bar();
  var x = 1;
}

is

var x;
if (...) {
  bar();
  x = 1;
}

this has unexpected consequences, for example:

var foo = 1;
function bar() {
  if (!foo) {
    var foo = 2;
  }
  console.log(foo);
}
bar();

here this will log 2 since it’s interpreted as:

var foo = 1;
function bar() {
  var foo; // foo is a new undefined variable
  if (!foo) {
    foo = 2;
  }
  console.log(foo);
}
bar();

There are some rules that makes this trickier. First, a function declaration (function foo(){...}) takes priority over a variable declaration (var foo = 1).

In this case:

var foo = 1;
function foo(){};
console.log(foo);

this will log 1. It is like if it was written like:

var foo = function(){};
foo = 1;
console.log(foo);

Here:

var foo = 1;
function bar() {
  foo = 2;
  return;
  function foo() {}
}
bar();
console.log(foo);

this will log 1, since this is like if we had

var foo = 1;
function bar() {
  var foo = function(){};
  foo = 2;
  return;
}
bar();
console.log(foo);

so the line foo=2 is not changing the variable in the parent scope but its new independent foo

Other interesting thing is that with a function declaration everything get’s lifted to the top, the name and the body of the function, instead of just the name. This is for the function declarations (function foo() { ... }), not the function expressions (var foo = function foo() { .... }), that behave like the others.

Here i get lost with the terminology some times:

// variable declaration
var foo = 1;

// Function declaration
function foo() { return 1; }

// Anonymous function expression
var foo = function() { return 1; }

// Named function expression
var foo = function foo() { return 1; }

So

foo();
function foo(){ return 1}

returns 1, but

foo();
var foo = function foo(){ return 1};

fails


Shadowing

Imagine that you had a variable, and you wanted to use another with the same name inside a method, in that case you would shadow the outer variable like this:

var foo = 1;

function(){
  var foo = 2; // this variable is independent
}

This works with functions, not with within an if for example. In coffeescript you have to be keep in mind that you might be altering another variable in the same script since you can’t shadow them using var, but there are other options:

1) If you can use ECMAScript 6, you have the option to use a block scope with let:

var foo = 1;

if (...){
  let foo = 2; // this variable is also independent
}

2) Use a Immediately-Invoked Function Expression (IIFE) = self-executing anonymous function (or self-invoked anonymous function) (function() { ... })();

var foo = 1;
if (...){
  (function(){
    var foo = 2; // this variable is also independent
  }()
}

Closure

This is the fact that a function that is defined inside another, will keep access to all the variables of this parent function, even after this parent function exits.

Let’s say that you have something like

function makeCounter() {
  var i = 0;

  function tellMeTheValue() {
    alert(i);
  }

  setTimeout(tellMeTheValue, 10000);  

  return function increaseByOne() {
    console.log( ++i );
  };
}

counter = makeCounter();
counter(); // logs: 1
counter(); // logs: 2
// 10 seconds after making the counter we get an alert with the latest value

Related: This is bug that we had, in which were executing a series of functions with a setTimeout without realizing that the reference to the function was being replaced (instead of creating a closure to preserve them)

events = {
  foo: function() {
    return console.log("foo");
  },
  bar: function() {
    return console.log("bar");
  }
};

applyEvents = function() {
  var eventHandler, eventName, results;
  for (eventName in events) {
    eventHandler = events[eventName];
    setTimeout(function() {
      eventHandler();
    });
  }
};

applyEvents2 = function() {
  var eventHandler, eventName, results;
  for (eventName in events) {
    eventHandler = events[eventName];
    (function(_eventHandler) {
      return setTimeout(function() {
        _eventHandler();
      });
    })(eventHandler)
  }
};

See the Pen javascript issue by Fernando Sainz (@fsainz) on CodePen.


Links: