Context in Javascript:

Scope, IIFE, Modules & Packages

JS Scope & Hoisting

Global

Function

Lexical

// globally scoped
var deepsAnswer = 42;
var deepQuestion = function() {
  // locally scoped to the function deepQuestion()
  var q = "Answer to the Ultimate Question of Life, the Universe, and Everything";

  // deepQuestion() has access to the outer scope here!
  console.info(deepsAnswer);

  return q;
};

// console - lexical scoping is one-way!
var question = deepQuestion();
// > 42
question
// > "Answer to the Ultimate Question of Life, the Universe, and Everything"
console.log(q); 
// > Uncaught ReferenceError: q is not defined
var foo = 'foo';

var scope1 = function () {
  // foo is available here!
  var bar = 'bar';

  var scope2 = function () {
    // foo is available here too, and so is bar!
    var baz = 'baz';
    
    var scope3 = function () {
      // foo is also available here, and so are bar and baz!
      var qux = 'qux';
    };
  };
};

A name can enter a scope in one of 4 different ways (in this order, innermost to outermost scope)

  • this and arguments by default
  • Named parameters
  • Function declarations
  • Variable declarations

Hoisting

It's important to be aware of the fact that Javascript separates the declaration of a variable from its assignment and moves all declarations to the top of the current scope. This behavior is called 'hoisting' because declarations are hoisted up to the top of scope. Functions are hoisted first (referred to as function hoisting), then variables (referred to as variable hoisting). If you run into strange TypeErrors, it could be due to hoisting!

// How you write it
function getFuncay() {
  getTheFunc(); // TypeError
  outtaMyFace(); // "outta my FACE!"

  var getTheFunc = function() {
    console.log("Get the FUNC...");
  };

  function outtaMyFace() {
    console.log("outta my FACE!");
  }
}

// How JS interprets it
function getFuncay() {

  // function hoisting!
  function outtaMyFace() {
    console.log("outta my FACE!");
  }

  // variable hoisting!
  var getTheFunc;

  getTheFunc(); // TypeError
  outtaMyFace(); // "outta my FACE!"

  // assignment comes last!
  getTheFunc = function() {
    console.log("Get the FUNC...");
  };
}

Closures

Closures close over the parent scope and can be named or anonymous functions. They're mostly used to calculate some value and then make that value available to the parent scope but are also used for callbacks and can be called directly.

var greet = function(name) {  
  var makeGreeting = function() {
    var makeRobotName = function() {
      return name.split(' ')[0] + 
        'Bot' + 
        Math.floor(Math.random() * (100 - 1)) + 1;
    };
    return 'Hello, ' + makeRobotName() + '!';
  };
  return makeGreeting();
};

greet('Carlo');
// > Hello, CarloBot854!

var greet = function(name) { 
  return function() {
    var makeRobotName = function() {
      return name.split(' ')[0] + 
        'Bot' + 
        Math.floor(Math.random() * (100 - 1)) + 1;
    };
    return 'Hello, ' + makeRobotName() + '!';
  };
};

var greeting = greet('Carlo');
greeting();
// > Hello, CarloBot 749!

greet('Carlo')();
// > Hello, CarloBot 219!

this

this is bound based on how the function is invoked. By default, this binds to the outermost global object. 

var funcay = function() {
  // this is the global Object, usually window
  console.log(this);
};

var theFunc = {};
theFunc.funcay = function() {
  // this is theFunc Object
  console.log(this);
};

var myList = $('#myList');
var toggleList = function() {
  // this is the myList object
  console.log(this);
  setTimeout(function() {
    // new function scope! this is window!
    // below won't work as intended
    console.log(this);
    this.is('hidden') ? this.show() : this.hide();
  }
};
myList.addEventListener('click', toggleList, false);

var toggleList = function() {
  // this is the myList object
  console.log(this);
  var self = this;
  setTimeout(function() {
    // now, we can use self to get what we want
    console.log(self); // myList
    self.is('hidden') ? self.show() : self.hide();
  }
};
myList.addEventListener('click', toggleList, false);

Changing scope

call() and apply() let you invoke your function, passing in scope and arg(s). call() takes a list of comma-separated args. apply() an array of args. Another function like these is bind(), which also takes args but just binds them and does not invoke (call) the function. Since all of these take a value of this (scope) as an argument, we can switch in and out of scopes as needed this way.

function foo() { console.log(this); }

// binds this, passes args, invokes function
foo.call(this, thing1, thing2);

// binds this, passes array of args
// invokes function
foo.apply(this, [thing1, thing2]);

// binds this, passes args
// does not invoke function
foo.bind(this, thing1, thing2);

function declareProperties() {
  var declaration = [this.name, 'is a', this.job]
    .join(' ');
  console.log(declaration);
}

var human = {
  name: 'Han Solo', 
  job: 'scruffy-looking nerf herder'
};

// Han Solo is a scruffy-looking nerf herder
declareProperties.call(human);
declareProperties.apply(human);

whatsHan = declareProperties.bind(human);

// Han Solo is a scruffy-looking nerf herder
whatsHan();

// you could also do this, which 
// is effectively a call()
declareProperties.bind(human)();

Important rules to remember about scope:

  • Lexical scope means that anything defined in a function's parent is visible in its entire scope chain! 
  • Lexical scoping works in one direction!
  • Javascript evaluates scope from the inside out
  • Closures allow you to return a value so that it's available in the parent scope
  • All scope in Javascript is either global or function scope
  • Variables are hoisted, meaning that they're declared under the hood at the top of their scope!
  • In Javascript, despite it looking like C, scope is function scope, NOT block scope! 
  • this is bound to the outermost global object by default!
  • It's easy to control scope by using this, call(), bind() and apply()

Changes in ES6/ES7

  • let keyword introduced to allow block scoping and does not create a variable on the global object

  • const keyword introduced to allow block scoped variables whose entire value cannot be reassigned

IIFE & Modules

IIFE

A common pattern is to create an anonymous function that encapsulates our function expression, then immediately call it. This technique is the basis of the module pattern in javascript and is called an IIFE (pronounced "iffy"), which stands for Immediately Invoked Function Expression. 

// IIFE syntax
(function () {
  // private scope!
})(); // <-- call it!

// Let's make a real one - this runs and logs
// the string "Hi!", as expected
(function() {
  var sayHi = function() {
    console.log("Hi!");
  };

  sayHi();
})();

// but when we try to call sayHi(), we get
// Uncaught ReferenceError: sayHi is not defined
sayHi();

Module Pattern

The module pattern effectively binds the anonymous function to a variable, which allows it to be called later and passed as a value. The revealing module pattern expands on this idea by returning an object, exposing the publicly accessible properties and methods and hiding the properties and methods that you want to keep private.

// To make an IIFE a module, bind it to a var 
// and have it return an object that stores 
// your methods
var Greeter = (function() {
  return {
    sayHi: function() {
      console.log("Hi!");
    };
  }
})();

// "Hi!"
Greeter.sayHi();

// A better way to do this, with an example
// of a private method
var Greeter = (function() {
  var sayHi = function() {
    console.log("Hi!");
  };

  var sayBye = function() {
    console.log("Bye!");
  };

  return {
    sayHi: sayHi
  }
})();

// "Hi!"
Greeter.sayHi();

// Uncaught TypeError: Greeter.sayBye 
// is not a function
Greeter.sayBye();

module.exports

In the NodeJS ecosystem, including client-side libraries that now use Node-style CommonJS-based package management, you'll often see the module pattern used and bound to a variable called module.exports.

// Imagine that this gets run every time you
// start up your application. This creates the
// global objects exports and module.exports and
// initializes them to an empty object if undefined
var exports = module.exports = {};

// Now, we have our greeter module from before
var Greeter = (function() {
  var sayHi = function() {
    console.log("Hi!");
  };

  var sayBye = function() {
    console.log("Bye!");
  };

  return {
    sayHi: sayHi
  }
})();

// To make this available for importing, we bind 
// it to the global module.exports
module.exports = Greeter;

Module imports

Because this is a CommonJS module, to import it, you just have to use require().

// Our Greeter module
var Greeter = (function() {
  ...
})();

// To make this available for importing, we bind 
// it to the global module.exports
module.exports = Greeter;

// in another file, import the module
var Greeter = require('./greeter.js');

// we could easily import the same module as
var SparklyUnicorns = require('./greeter.js');

Future JS

In future versions of JS, the entire module system is getting overhauled and imports/exports get much nicer and more intuitive.

// Our Greeter module
export default function() {
  ...
}

// in another file, import the module
import greeter from './greeter';
greeter();

// or, get classy
export default class {
  ...
}

// in another file, import the class
import Greeter from './greeter';

// instantiate it
const instance = new Greeter();

// new style imports also support aliases
import * from './greeter' as SparklyUnicorn;
const friendlyUnicorn = new SparklyUnicorn();

JS Packages

F

F

F

F

F

M

F

F

F

F

F

M

F

F

F

F

F

M

​Package

Javascript Scope, IIFEs and the Module Pattern

By Carlo DiCelico

Javascript Scope, IIFEs and the Module Pattern

In this workshop, we'll be learning about scoping rules in javascript, variable hoisting, immediately-invoked function expressions (IIFE) and the module pattern. For homework, we'll be combining what we learned about prototypes with javascript's scoping rules and the module pattern to build a simple browser-based app. This workshop concludes the basic building blocks of javascript. Once we've worked through this, we'll be moving on to more advanced topics.

  • 904