The central theme of this book has been that understanding and mastering scope and closure is key in properly structuring and organizing our code, especially the decisions on where to store information in variables.
Encapsulation and Least Exposure (POLE)
Encapsulation is often cited as a principle of object-oriented (OO) programming, but it’s more fundamental and broadly applicable than that. The goal of encapsulation is the bundling or co-location of information (data) and behavior (functions) that together serve a common purpose.What Makes Encapsulation
Independent of any syntax or code mechanisms, the spirit of encapsulation can be realized in something as simple as using separate files to hold bits of the overall program with common purpose. Another key goal is the control of visibility of certain aspects of the encapsulated data and functionality. In JS, we most often implement visibility control through the mechanics of lexical scope. The natural effect of this effort is better code organization. It’s easier to build and maintain software when we know where things are, with clear and obvious boundaries and connection points.What Is a Module?
A module is a collection of related data and functions (often referred to as methods in this context), characterized by a division between hidden private details and public accessible details, usually called the “public API.” A module is also stateful: it maintains some information over time, along with functionality to access and update that information.Namespaces (Stateless Grouping)
If you group a set of related functions together, without data, then you don’t really have the expected encapsulation a module implies. The better term for this grouping of stateless functions is a namespace:Utils here is a useful collection of utilities, yet they’re all state-independent functions. Gathering functionality together is generally good practice, but that doesn’t make this a module. Rather, we’ve defined a Utils namespace.
Data Structures (Stateful Grouping)
Even if you bundle data and stateful functions together, if you’re not limiting the visibility of any of it, then you’re stopping short of the POLE aspect of encapsulation:records is publicly accessible data, not hidden behind a public API, Student here isn’t really a module. It’s best to label this an instance of a data structure.
Modules (Stateful Access Control)
To embody the full spirit of the module pattern, we not only need grouping and state, but also access control through visibility (private vs. public). Let’s turnStudent into a module. We’ll start with a form called the “classic module”:
Student is now an instance of a module. It features a public API with a single method: getName(..). This method is able to access the private hidden records data.
The instance of the module is created by the
defineStudent() IIFE being executed. This IIFE returns an object (named publicAPI) that has a property on it referencing the inner getName(..) function.From the outside, Student.getName(..) invokes this exposed inner function, which maintains access to the inner records variable via closure.Classic Module Definition
So to clarify what makes something a classic module:- There must be an outer scope, typically from a module factory function running at least once.
- The module’s inner scope must have at least one piece of hidden information that represents state for the module.
- The module must return on its public API a reference to at least one function that has closure over the hidden module state.
Module Factory (Multiple Instances)
If we want to define a module that supports multiple instances, we can slightly tweak the code:defineStudent() as an IIFE, we just define it as a normal standalone function, commonly referred to as a “module factory” function.
Node CommonJS Modules
Unlike the classic module format, where you could bundle the module factory or IIFE alongside any other code, CommonJS modules are file-based; one module per file. Let’s tweak our module example to adhere to that format:records and getName identifiers are in the top-level scope of this module, but that’s not the global scope. Everything here is by default private to the module.
To expose something on the public API of a CommonJS module, you add a property to the empty object provided as module.exports.
Some developers have the habit of replacing the default exports object. As such, I recommend against replacing the object. If you want to assign multiple exports at once, you can do this:
require(..) method:
Student now references the public API of our example module.
CommonJS modules behave as singleton instances, similar to the IIFE module definition style. No matter how many times you
require(..) the same module, you just get additional references to the single shared module instance.require(..) is an all-or-nothing mechanism. To effectively access only part of the API:
Modern ES Modules (ESM)
The ESM format shares several similarities with the CommonJS format. ESM is file-based, and module instances are singletons, with everything private by default. One notable difference is that ESM files are assumed to be strict-mode, without needing a"use strict" pragma at the top.
Instead of module.exports in CommonJS, ESM uses an export keyword to expose something on the public API of the module. The import keyword replaces the require(..) statement:
export { getName } statement. export statements can appear anywhere throughout the file, though export must be at the top-level scope.
ESM Export Variations
ESM offers variation on how theexport statements can be specified:
export appears before the function keyword here, this is still a function declaration that also happens to be exported. The getName identifier is function hoisted, so it’s available throughout the whole scope of the module.
Another allowed variation:
default exports are referred to as “named exports.”
ESM Import Variations
Theimport keyword has a number of variations in syntax. The first is referred to as “named import”:
{ .. } set, separated with commas. A named import can also be renamed with the as keyword:
getName is a “default export” of the module:
{ } around the import binding.
By contrast, the other major variation on import is called “namespace import”:
* imports everything exported to the API, default and named, and stores it all under the single namespace identifier as specified. This approach most closely matches the form of classic modules.
Exit Scope
Whether you use the classic module format (browser or Node), CommonJS format (in Node), or ESM format (browser or Node), modules are one of the most effective ways to structure and organize your program’s functionality and data.The module pattern is the conclusion of our journey in this book of learning how we can use the rules of lexical scope to place variables and functions in proper locations. POLE is the defensive private by default posture we always take.And underneath modules, the magic of how all our module state is maintained is closures leveraging the lexical scope system.

