Chapter 3: Scope and Closure

Scope

Scope is all about whether you can access variables or not. In Javascript, scope is determined by functions! Any variable you declare inside a functions, stay in that function. You're not able to access them. On the other hand, inside the function you can still access any variable that was declared outside it. 

If you use C or Java, you might be used to block scope. This does not exist in the current version Javascript (ES5). Writing block scopes is valid syntax, but it's not recommended, because it won't behave like you expect to.

Let's take a look at a little example.

var logNumber = function () {
  var number = 2;
  console.log(number);
};

console.log(number); // Output: undefined
logNumber(); // Output: 2

A var called "number" was declared inside the function "logNumber". There is absolutely no way to reach "var number" outside of function "logNumber"!

The other way around however, is possible. The inner scope is able to access anything that was declared outside of it.

var number = 2;
var logNumber = function () {
  console.log(number);
};

logNumber(); // Output: 2, no problem!

Exercise: could tell the output of the following example?

var logA = function () {
    var a = 1;
    console.log(a);
    console.log(b);
};
var logB = function () {
    var b = 2;
    console.log(a);
    console.log(b);
};

logA();
logB();

Global scope

Any variable declared in the top scope is the global scope, it will be available anywhere. 

One way to check if a variable is the global scope, is by the window object (note: this is a browser quirk). Any global variable will become a property of window. This sounds a bit strange, but the window object is equivalent to the global scope.

// global scope, aka the window object
var hello = "hi";

if (window.hello === hello) {
  console.log('Yup, you just made window.hello');
  console.log(window.hello); // 'hi'
}

Be warned! Here is a JavaScript gotcha. If you assign a variable that was never declared with a var, JavaScript will assume this variable is global!!

// not writing var
hello = "hi";
console.log(hello === window.hello); // Output: true

Particularly important when you do this inside a function. Don't make this mistake, thinking you made a variable scoped to a function:

// inside a function scope
var fn = function () {
  // Oops! Forgot the var here
  oops = 123;
};
fn();

// can we access oops?
console.log(oops); // Output: 123

Nesting Functions

You can declare functions within functions. The same rules apply, the most inner function can still access the variables outside, but you cannot reach variables that are inside.

var global = "I'm global";

var outerFunction = function () {
    var outer = "I'm in the outer function";
    var innerFunction = function () {
        var inner = "I'm the inner var";
        console.log(outer); // can reach the outer var
        console.log(global); // can reach the global too
    };
    console.log(inner); // undefined, we can't reach this inner var
};

Declared variables before Invoking functions

Functions can be declared and assigned to vars, but that does not mean they are invoked. You can use variables even after they are declared later, as long as they are in the same scope;

var log = function () {
  console.log(value);
};
// note that we declare var value AFTER var log
var value = 10;

log(); // Output: 10

It's important to understand how the code flows here. JavaScript won't look inside those function until you invoke them, so the example did not access any variable that wasn't declared. The example shows how that doesn't work:

// it does not work like this:
var cat = {
  age: year
};
var year = 5;

console.log(year); // 5
console.log(cat.age); // undefined

If you want to understand this better, this video might help: https://www.youtube.com/watch?v=QyUFheng6J0

Closures

Closures are perhaps the most powerful yet confusing feature in Javascript. It may be difficult to understand at first, but once you understand this concept, you will have tackled the biggest part of Javascript.

Consider the following function:

var outerFunction = function () {
  var innerVar = 10;
  var innerFunction = function () {
    innerVar += 1;
    console.log("innerVar is " + innerVar);
  };
  
  return innerFunction;
};

The innerFunction is declared inside the outerFunction, but is not invoked. The innerFunction has access to innerVar, it's in the same scope after all. What happens if we invoke the outer function?

var inF = outerFunction();

The outerFunction is executed and returns the innerFunction. We now have a reference to that inner function.

In other programming languages, everything inside the outerFunction would out of memory after the execution of the function ends. It's all gone! In Javascript it is not, as a matter of fact if we now invoke the innerFunction several times:

inF(); // innerVar is 11
inF(); // innerVar is 12
inF(); // innerVar is 13
inF(); // innerVar is 14

Which clearly indicates the innerVar is still alive and well. The outerFunction has created a closure. Every function will do this. Once a function is invoked, all variables inside it will remain in memory.

Hoisting

With function scopes and closures, hoisting is more of a logical consequence. Hoisting is something the browser does when it compiles the Javascript for interpretation. All variables are placed on the top of the current scope (function). For example

var cat = {};
var setCatName = function () {
  cat.name = '';
};

setCatName();

var name = "Nom Cat";
cat.name = name;

What the compiler will actually do is:

var cat = {},
    setCatName = function () {
      cat.name = '';
    },
    name;

setCatName();

name = "Nom Cat";
cat.name = name;
It is normally good practice to declare variables as close as possible to the point where you use the variable. Unfortunately, that is not the case with Javascript. With the hoisting it is better to always declare variables on the top!

Gotchas

Closures are incredibly powerful, but also easy to make mistakes with. Two traps when working with closures: (for) loops and asynchronous callbacks. Take a look at this example of preloading images:

var startGame = function () {
  console.log("Start game!");
};
    
// preload some assets!
for (var i = 0; i < 10; ++i) {
  var image = new Image();
  image.src = "assets/image" + i + ".png";
  image.onload = function () {
    console.log("Loaded image number " + i);
    // done loading all assets
    if (i >= 9) {
        startGame();
    }
  };
}

Pretty cool, right? Just wait until you see the output:

Loaded image number 10
Start game!
Loaded image number 10
Start game!
Loaded image number 10
Start game!
Loaded image number 10
Start game!
Loaded image number 10
Start game!
Loaded image number 10
Start game!
Loaded image number 10
Start game!
Loaded image number 10
Start game!
Loaded image number 10
Start game!
Loaded image number 10
Start game!

😱 What happened here?!

i seems to be 10 all the time and startGame is called 10 times.

Let's rewrite this part of code to better reflect what is happening:

var startGame = function () {
  console.log("Start game!");
};
var i;
var image;
var onload = function () {
  console.log("Loaded image number " + i);
  // done loading all assets
  if (i >= 9) {
    startGame();
  }
};

// preload some assets!
for (i = 0; i < 10; ++i) {
  image = new Image();
  image.src = "assets/image" + i + ".png";
  image.onload = onload;
}

One very important thing to reiterate here. There is no such thing as block scope, only function scope! Writing var inside the for loop has absolutely no effect. What really is happening is that we are redefining image 10 times. In the previous example we were also redefining the same function 10 times in onload. We might as well define the onload function on top once and assign it to each image.

Let's take a look at what is actually happening. We are indeed loading 10 different images, however onload is called much later, when the images are loaded. It could be a few milliseconds, or longer, but it could definitely not be so fast that it finished loading during the for-loop. So the for loop ends, i is not assigned as 10 and the browser tries to load images. By the time the images are loaded, onload is called, but the closure still exists! i remains 10 and so it logs "Loaded image number 10" for every image.

If we were to fix this, we simply need to make sure the correct value of i is used the moment onload is called. We could do this by passing i as a parameter.

var startGame = function () {
  console.log("Start game!");
};
var i;
var image;
var addCallback = function (j) {
  var loadedCount = 0;
  image.onload = function () {
    console.log("Loaded image number " + j);
    loadedCount += 1;
    // done loading all assets
    if (loadedCount >= 9) {
      startGame();
    }
};

// preload some assets!
for (i = 0; i < 10; ++i) {
  image = new Image();
  image.src = "assets/image" + i + ".png";
  addCallback(i);
}