IIFE and Module Pattern
Immediately Invoked Function Expressions (IIFE) are a powerful JavaScript pattern that creates isolated scopes and enables modular code organization. In this lesson, we'll explore how IIFE works and how it forms the foundation of the Module Pattern, one of the most important design patterns in JavaScript.
What is an IIFE?
An IIFE (pronounced "iffy") is a function that is defined and immediately executed. It's a way to create a private scope and avoid polluting the global namespace.
// Basic IIFE syntax
(function() {
console.log("This runs immediately!");
})();
// Arrow function IIFE
(() => {
console.log("Arrow IIFE also works!");
})();
// IIFE with parameters
(function(name) {
console.log(`Hello, ${name}!`);
})("John");
Key Concept: The parentheses around the function (function() {...}) turn it into a function expression, and the trailing () invokes it immediately.
Why Use IIFE?
IIFEs solve several important problems in JavaScript:
// Problem: Global namespace pollution
var counter = 0;
var increment = function() { counter++; };
var decrement = function() { counter--; };
// Solution: IIFE creates private scope
(function() {
var counter = 0; // Private variable
window.increment = function() { counter++; return counter; };
window.decrement = function() { counter--; return counter; };
})();
console.log(counter); // undefined (private!)
console.log(increment()); // 1
console.log(increment()); // 2
IIFE Syntax Variations
There are several ways to write IIFEs:
// Standard IIFE
(function() {
console.log("Standard IIFE");
})();
// Alternative syntax (Douglas Crockford style)
(function() {
console.log("Alternative IIFE");
}());
// Named IIFE (useful for debugging)
(function myIIFE() {
console.log("Named IIFE");
})();
// Unary operators (less common)
!function() {
console.log("Using ! operator");
}();
+function() {
console.log("Using + operator");
}();
void function() {
console.log("Using void operator");
}();
Tip: The standard (function() {})() syntax is the most readable and widely used. Stick with this unless you have a specific reason to use alternatives.
IIFE with Return Values
IIFEs can return values, which is crucial for the Module Pattern:
const calculator = (function() {
// Private variables
const pi = 3.14159;
// Private function
function validate(num) {
return typeof num === 'number' && !isNaN(num);
}
// Public API
return {
add: function(a, b) {
if (validate(a) && validate(b)) {
return a + b;
}
return "Invalid input";
},
circleArea: function(radius) {
if (validate(radius)) {
return pi * radius * radius;
}
return "Invalid radius";
}
};
})();
console.log(calculator.add(5, 3)); // 8
console.log(calculator.circleArea(10)); // 314.159
console.log(calculator.pi); // undefined (private)
console.log(calculator.validate); // undefined (private)
The Module Pattern
The Module Pattern uses IIFE to create modules with private and public members. It's one of the most important patterns for organizing JavaScript code:
const UserModule = (function() {
// Private members
let users = [];
let currentId = 1;
function validateUser(user) {
return user.name && user.email;
}
function generateId() {
return currentId++;
}
// Public API
return {
addUser: function(name, email) {
const user = { id: generateId(), name, email };
if (validateUser(user)) {
users.push(user);
return user;
}
return null;
},
getUser: function(id) {
return users.find(user => user.id === id);
},
getAllUsers: function() {
// Return copy to prevent direct modification
return users.map(user => ({ ...user }));
},
updateUser: function(id, updates) {
const user = users.find(u => u.id === id);
if (user) {
Object.assign(user, updates);
return true;
}
return false;
},
deleteUser: function(id) {
const index = users.findIndex(u => u.id === id);
if (index !== -1) {
users.splice(index, 1);
return true;
}
return false;
},
getUserCount: function() {
return users.length;
}
};
})();
// Usage
const user1 = UserModule.addUser("John Doe", "john@example.com");
const user2 = UserModule.addUser("Jane Smith", "jane@example.com");
console.log(UserModule.getAllUsers());
console.log(UserModule.getUserCount()); // 2
Revealing Module Pattern
The Revealing Module Pattern is a variation that makes the code more organized by defining all functions first and revealing them at the end:
const ShoppingCart = (function() {
// Private variables
let items = [];
let total = 0;
// Private functions
function calculateTotal() {
total = items.reduce((sum, item) => sum + item.price * item.quantity, 0);
return total;
}
function findItem(id) {
return items.find(item => item.id === id);
}
// Public functions
function addItem(id, name, price, quantity = 1) {
const existingItem = findItem(id);
if (existingItem) {
existingItem.quantity += quantity;
} else {
items.push({ id, name, price, quantity });
}
calculateTotal();
return { added: true, total: getTotal() };
}
function removeItem(id) {
const index = items.findIndex(item => item.id === id);
if (index !== -1) {
items.splice(index, 1);
calculateTotal();
return true;
}
return false;
}
function updateQuantity(id, quantity) {
const item = findItem(id);
if (item) {
item.quantity = quantity;
calculateTotal();
return true;
}
return false;
}
function getItems() {
return items.map(item => ({ ...item }));
}
function getTotal() {
return total;
}
function clearCart() {
items = [];
total = 0;
}
// Reveal public API
return {
addItem: addItem,
removeItem: removeItem,
updateQuantity: updateQuantity,
getItems: getItems,
getTotal: getTotal,
clearCart: clearCart
};
})();
// Usage
ShoppingCart.addItem(1, "Laptop", 999, 1);
ShoppingCart.addItem(2, "Mouse", 29, 2);
console.log(ShoppingCart.getItems());
console.log(ShoppingCart.getTotal()); // 1057
Advantage: The Revealing Module Pattern makes it clear which functions are public (revealed) and which are private, improving code readability and maintenance.
Namespace Pattern
IIFEs can be used to create namespaces, preventing naming conflicts:
// Creating a namespace
const MyApp = MyApp || {};
// Adding modules to namespace
MyApp.utilities = (function() {
function formatDate(date) {
return date.toLocaleDateString();
}
function formatCurrency(amount) {
return `$${amount.toFixed(2)}`;
}
return {
formatDate: formatDate,
formatCurrency: formatCurrency
};
})();
MyApp.validators = (function() {
function isEmail(email) {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}
function isPhoneNumber(phone) {
return /^\d{10}$/.test(phone);
}
return {
isEmail: isEmail,
isPhoneNumber: isPhoneNumber
};
})();
// Usage
console.log(MyApp.utilities.formatCurrency(1234.5)); // $1234.50
console.log(MyApp.validators.isEmail("test@example.com")); // true
Module Dependencies
Modules can depend on other modules by passing them as parameters:
// jQuery-like module
const $ = { ajax: function() { console.log("Ajax call"); } };
// Module that depends on $
const DataService = (function($) {
function fetchUsers() {
$.ajax();
return ["User1", "User2", "User3"];
}
function fetchPosts() {
$.ajax();
return ["Post1", "Post2"];
}
return {
fetchUsers: fetchUsers,
fetchPosts: fetchPosts
};
})($); // Pass $ as dependency
console.log(DataService.fetchUsers());
// Multiple dependencies
const ComplexModule = (function($, UserModule, DataService) {
function initialize() {
const users = DataService.fetchUsers();
console.log(`Loaded ${users.length} users`);
}
return {
init: initialize
};
})($, UserModule, DataService);
Best Practice: Explicitly passing dependencies makes your modules more testable and clearly documents what each module needs.
IIFE for Configuration
IIFEs are great for creating configurable modules:
const Logger = (function(config) {
const settings = {
enabled: config.enabled !== undefined ? config.enabled : true,
level: config.level || 'info',
prefix: config.prefix || '[LOG]'
};
const levels = {
debug: 0,
info: 1,
warn: 2,
error: 3
};
function shouldLog(level) {
return settings.enabled && levels[level] >= levels[settings.level];
}
function formatMessage(level, message) {
const timestamp = new Date().toISOString();
return `${settings.prefix} [${level.toUpperCase()}] ${timestamp}: ${message}`;
}
return {
debug: function(message) {
if (shouldLog('debug')) {
console.log(formatMessage('debug', message));
}
},
info: function(message) {
if (shouldLog('info')) {
console.log(formatMessage('info', message));
}
},
warn: function(message) {
if (shouldLog('warn')) {
console.warn(formatMessage('warn', message));
}
},
error: function(message) {
if (shouldLog('error')) {
console.error(formatMessage('error', message));
}
}
};
})({ enabled: true, level: 'info', prefix: '[MyApp]' });
Logger.debug("This won't show"); // Level is info
Logger.info("Application started");
Logger.warn("Low memory");
Logger.error("Connection failed");
ES6 Modules vs IIFE Modules
Modern JavaScript has native modules, but understanding IIFE modules is still valuable:
// IIFE Module (ES5 compatible)
const MathUtils = (function() {
function add(a, b) { return a + b; }
function multiply(a, b) { return a * b; }
return { add, multiply };
})();
// ES6 Module (modern approach)
// In mathUtils.js
export function add(a, b) { return a + b; }
export function multiply(a, b) { return a * b; }
// In main.js
import { add, multiply } from './mathUtils.js';
When to use each:
- Use ES6 modules for modern projects with build tools
- Use IIFE modules for legacy browser support
- Use IIFE for quick scripts without build process
- IIFE modules work in all browsers without transpilation
Important: While ES6 modules are the future, IIFE modules are still widely used in legacy code and libraries. Understanding both is essential for a well-rounded JavaScript developer.
Common IIFE Use Cases
Here are practical scenarios where IIFEs excel:
// 1. Avoiding variable conflicts in global scope
(function() {
var $ = "My custom $ variable";
console.log($); // Doesn't conflict with jQuery's $
})();
// 2. Creating unique counters
const counter1 = (function() {
let count = 0;
return () => ++count;
})();
const counter2 = (function() {
let count = 0;
return () => ++count;
})();
console.log(counter1()); // 1
console.log(counter1()); // 2
console.log(counter2()); // 1 (independent counter)
// 3. One-time initialization
(function initialize() {
const config = loadConfiguration();
setupEventListeners();
initializeApp(config);
console.log("App initialized");
})();
// 4. Protecting code from modification
const API = (function() {
const API_KEY = "secret-key-12345";
function makeRequest(endpoint) {
return fetch(endpoint, {
headers: { 'Authorization': `Bearer ${API_KEY}` }
});
}
return { makeRequest };
})();
// API_KEY is completely inaccessible from outside
Practice Exercise:
Challenge: Create a TodoList module using the Revealing Module Pattern. It should have private storage for todos and public methods to add, remove, toggle completion, and get all todos.
Solution:
const TodoList = (function() {
// Private state
let todos = [];
let nextId = 1;
// Private functions
function findTodo(id) {
return todos.find(todo => todo.id === id);
}
// Public functions
function addTodo(text) {
const todo = {
id: nextId++,
text: text,
completed: false,
createdAt: new Date()
};
todos.push(todo);
return todo;
}
function removeTodo(id) {
const index = todos.findIndex(todo => todo.id === id);
if (index !== -1) {
const removed = todos.splice(index, 1)[0];
return removed;
}
return null;
}
function toggleTodo(id) {
const todo = findTodo(id);
if (todo) {
todo.completed = !todo.completed;
return todo;
}
return null;
}
function getTodos(filter = 'all') {
if (filter === 'completed') {
return todos.filter(t => t.completed);
}
if (filter === 'active') {
return todos.filter(t => !t.completed);
}
return todos.slice(); // Return copy
}
function clearCompleted() {
const beforeLength = todos.length;
todos = todos.filter(t => !t.completed);
return beforeLength - todos.length;
}
// Reveal public API
return {
add: addTodo,
remove: removeTodo,
toggle: toggleTodo,
getTodos: getTodos,
clearCompleted: clearCompleted
};
})();
// Usage
TodoList.add("Learn JavaScript");
TodoList.add("Build a project");
TodoList.toggle(1);
console.log(TodoList.getTodos());
Summary
In this lesson, you learned:
- IIFE (Immediately Invoked Function Expression) creates isolated scopes
- IIFEs prevent global namespace pollution and variable conflicts
- The Module Pattern uses IIFE to create modules with private members
- The Revealing Module Pattern improves code organization and readability
- Namespace patterns help organize multiple modules
- Modules can accept dependencies as parameters
- IIFEs remain valuable even with ES6 modules
- Common use cases: encapsulation, one-time initialization, configuration
Next Up: In the next lesson, we'll master the 'this' keyword and learn how to control context in JavaScript!