Skip to main content

Factory Functions and the Module Pattern

Resource

I. Scopes

Resource

  • When a variable is not declared within any function, existing outside any { curly braces }, they are said to be in the global scope, meaning that they’re available everywhere.
  • If they are within a function or { curly braces }, they are locally scoped.

These braces can be of those of a for loop, if-else condition, function, or any other similar construct, and are called a block.

let globalAge = 23; // This is a global variable

// This is a function - and hey, a curly brace indicating a block
function printAge (age) {
let localAge = 34; // This is a function scoped variable

// This is yet another curly brace, and thus a block
if (age > 0) {
// This is a block-scoped variable that exists
// within its nearest enclosing block, the if's block
const constAge = age * 2;
console.log(constAge);
}

// ERROR! We tried to access a block scoped variable
// not within its scope
console.log(constAge);
}

printAge(globalAge);

// ERROR! We tried to access a function scoped variable
// outside the function it's defined in
console.log(localAge);

1. Closures

Resource

  • Functions in JavaScript form closures. A closure refers to the combination of a function and the surrounding state in which the function was declared.
  • This surrounding state, also called its lexical environment, consists of any local variables, access to outer scope variables, and access to global scope variables at the time the closure was made.
  • Lexical Environment = What variables a function can "see" and use
  • Closure = The function itself + its "memory" of those variables
let x = 10;

function outer(y) {
// outer's closure: function + access to x

return function inner() {
// inner's closure: function + access to x and y
return x + y;
}
}

const add5 = outer(5);
console.log(add5()); // 15

Think of a closure like a backpack that a function carries around with it. When you create a function inside another function, the inner function gets to pack up any variables it needs from its surroundings into its backpack.

**Example 1: **
function createGreeter(name) {
// 'name' goes into the inner function's "backpack"
return function() {
return "Hello, " + name + "!";
}
}

const greetBob = createGreeter("Bob");
const greetAlice = createGreeter("Alice");

console.log(greetBob()); // "Hello, Bob!"
console.log(greetAlice()); // "Hello, Alice!"
Example 2:
function makeAdding(firstNumber) {
const first = firstNumber;
// first goes into the "backpack"

return function(secondNumber) {
const second = secondNumber;
return first + second;
}
}

const add5 = makeAdding(5); // This function remembers 5
const add10 = makeAdding(10); // This function remembers 10

console.log(add5(3)); // 8 (5 + 3)
console.log(add10(3)); // 13 (10 + 3)
  • add5 is a function that always remembers the number 5
  • add10 is a different function that always remembers the number 10
  • Each function keeps its "backpack" of remembered values even after the original function (makeAdding) has finished running → In the current setup, you need to call it in two steps because the function is designed to take one parameter at a time.

2. Object Constructors

Object constructors look like regular JavaScript functions, even though they do not behave like regular functions. If you try to use a constructor function without the new keyword, the program will fail to work and produce error messages that are hard to track down and understand.

instanceof operator

The instanceof operator in JavaScript checks if an object an instance of a particular class or constructor function. The operator returns a boolean value.

Because instanceof checks the presence of a constructor’s prototype in an object’s entire prototype chain, it doesn’t confirm if an object was made with that constructor. This led to the use of a pattern that’s similar to constructor, but addresses this problem: Factory Functions.

II. Factory Functions

Factory functions work very similar to constructors, but with one key difference. Instead of using the new keyword to create an object like constructors, factory functions set up and return the new object when you call the function. Factory functions leverage closures to create private variables, each object gets its own methods, which makes it more flexible than constructors.

Factory functions do not use the prototype. The tradeoff here is memory efficiency, because factory functions create separate copies of methods for each objects, while constructors share methods through the prototype chain.

→ However, this only matters if you’re creating MANY objects. Factory functions' benefits (like closure-based private variables) often outweigh this small performance cost.

// define an object constructor
function User(name) {
this.name = name;
this.userName = "#" + name;
}

// define a factory function
function createUser(name) {
const userName = "#" + name;
// returning an object
return {
name,
userName
}
}

// with this, now, there are TWO ways to create a new user object instance
// 1. using the object constructor
const user1 = new User("user1");
console.log(user1); // log: { name: 'user1', userName: '#user1' }

// 2. instead of using the new keyword
const user2 = createUser("user2");
console.log(user2); // log: { name: 'user2', userName: '#user2' }

1. Private Variables and Functions

In this example:

  • The variables logInName, employeeEmail, and numberOfTasks are essentially private variables.
    • Specifically numberOfTasks, this variable can only be interacted/accessed with through the returned methods, because the object returned in the factory function doesn’t contain the numerOfTasks variable itself.
  • The returned object contains three functions, one that reads the value of the numberOfTasks variable, the other two increases or decrease its value by one.
// factory function that create a new employee object
function createEmployee(firstName, lastName) {
const logInName = firstName[0] + lastName;
const employeeEmail = logInName + "@companyemail.com"

// a private variable -- only accessible through methods
let numberOfTasks = 0;

// methods
const getNumOfTasks = () => numberOfTasks;
const addTask = () => numberOfTasks++;
const removeTask = () => {
if (numberOfTasks > 0) {
numberOfTasks--;
}
else {
console.log("Error!");
}
};

return { logInName, employeeEmail, getNumOfTasks, addTask, removeTask };
}

// create an employee object instance called janeDoe
const janeDoe = createEmployee("Jane", "Doe");
console.log(janeDoe);

console.log(janeDoe.logInName); //JDoe
console.log(janeDoe.employeeEmail); // JDoe@companyemail.com

janeDoe.removeTask(); // Error!
janeDoe.addTask(); // numberofTasks = 1
console.log(janeDoe.getNumOfTasks()); // log: 1

janeDoe.removeTask();
console.log(janeDoe.getNumOfTasks()); // log: 0

In factory functions, private variable or function uses closures to create smaller, dedicated variable and functions within a factory function itself - things that we do not need to return in the object itself.

→ We can create neater code without polluting the returned object with unnecessary variables that we create while creating the object itself.

2. “Prototypal Inheritance” with Factories

Let’s say, you need to create a Manager factory that inherits some methods from previously definedEmployee factory. Because factory functions do not use prototypal inheritance, this is what we can do:

function createManager(firstName, lastName, department) {
const { addTask, getNumOfTasks } = createEmployee(firstName, lastName);

return { firstName, lastName, department, addTask, getNumOfTasks };
}

const johnDoe = createManager("John", "Doe", "Finance");
console.log(johnDoe);

johnDoe.addTask(); // numberofTasks = 1
johnDoe.addTask(); // numberofTasks = 2
console.log(johnDoe.getNumOfTasks()); // logs 2

Object.assign method

This is a method in JavaScript used to copy the values of all enumerable properties from one or more source objects to a target object.

// syntax
Object.assign(target, source1, source2, ...)
// Example
// Copying properties from one object to another
const target = { a: 1, b: 2 };
const source = { b: 3, c: 4 };
const returnedObject = Object.assign(target, source);

console.log(target); // { a: 1, b: 3, c: 4 }
console.log(returnedObject); // { a: 1, b: 3, c: 4 }
// Create a new object without modifying the original
// target is a blank object
const originalObj = { x: 1, y: 2 };
const clonedObj = Object.assign({}, originalObj);

III. The Module Pattern: Immediately Invoked Function Expression (IIFEs)

Resource

1. IIFEs

IIFE stands for Immediately Invoked Function Expression. It's a function that runs as soon as it's defined.

// This is an IIFE
(function() {
console.log("I run immediately!");
})();

The key parts are:

  1. The outer parentheses (function(){...}) that wrap the function
  2. The trailing () that calls it immediately

Why IIFEs?

  • A problem that IIFEs help solve is protecting our variables.
  • For cases where you need multiple independent instances (like counters, user profiles, game characters, etc.), a factory function is more appropriate. The IIFE isn't "better" - it's just better suited for specific use cases where you want to ensure there's only one instance and it's created immediately.
Example 1:
  • Without IIFE:
// Without IIFE - everything is in global scope
let counter = 0;

function incrementCounter() {
counter += 1;
return counter;
}

// Any code can modify counter
counter = 100; // Oops! Someone changed our counter!
  • With IIFE:
// With IIFE - creates a private scope
const counter = (function() {
// This variable is private - only accessible inside this function
let count = 0;

// We return an object with methods to interact with our private variable
return {
increment: function() {
count += 1;
return count;
},
getCount: function() {
return count;
}
};
})();

// Now we can only interact with counter through its methods
console.log(counter.getCount()); // 0
counter.increment(); // 1
console.log(counter.getCount()); // 1

// We can't access or modify count directly
console.log(count); // ReferenceError: count is not defined

Example 2: Regular Factory Function vs. IIFE

IIFE creates one immediate instance (like a singleton) while the regular factory function lets you create multiple independent instances.

// createCounter regular Factory Function
function createCounter() {
let count = 0;

const increment = () => count++;
const getCount = () => count;

return {
increment, getCount
}
}

const counter1 = createCounter(); // Creates a counter obecjet instance
const counter2 = createCounter(); // Creates another counter object instance

counter1.increment(); // Increases count in counter1 instance
console.log(counter1.getCount()); // Output: 1 -> counter1 instance's count
console.log(counter2.getCount()); // Output: 0 -> counter2 instance's count is unaffected

// counter IIFE
const counter = (() => {
let count = 0;

const increment = () => count++;
const getCount = () => count;

return {
increment, getCount
}

})();

counter.increment();
counter.increment();

console.log(counter.getCount()); // Output: 2

2. The Module Pattern

A module is a way to encapsulate related code - both data and functions - and control what parts are public or private.

The pattern of wrapping a factory function inside a IIFE is called the module pattern.

Example:
  • We have a factory function creating some basic operations (add, sub, mul, div) that we need only once.
  • We wrap it in parentheses and immediately call it by adding () at the end, returning the result object in the calculator variable.
const calculator = (function () {
const add = (a, b) => a + b;
const sub = (a, b) => a - b;
const mul = (a, b) => a * b;
const div = (a, b) => a / b;

// return the object
return { add, sub, mul, div };
})();

calculator.add(3,5); // 8
calculator.sub(6,2); // 4
calculator.mul(14,5534); // 77476

→ This way, we can write code, wrapping away things that we do not need as private variables and functions inside our factory function. While they are tucked inside our module, we can use the returned variables and functions outside that factory, as necessary.

Encapsulating with the module pattern

Encapsulation is bundling data, code or something into a single unit, with select access to the things inside that unit itself. Basically, this means we don’t expose everything to the body of our program. Encapsulation leads to an effect called namespacing, which is a technique used to avoid naming collisions in our program.

Take the calculator example into consideration.

  • It’s very easy to imagine a scenario where you can accidentally create multiple functions with the name add.
  • What does add do - does it add two numbers? Strings?
  • Does it take its input directly from the DOM and display the result? What would you name the functions that do these things?
  • Instead, we can easily encapsulate them inside a module called calculator which generates an object with that name, allowing us to explicitly call calculator.add(a, b) or calculator.sub(a, b).