Chapter 5: Chapter 5. Functions for the master: closures and scopes
The introduction explains why closures matter in JavaScript and links their existence to the way JavaScript scopes work.
After this chapter you will be able to
- Practical value of closures. Explain how closures simplify complex JavaScript code, particularly code involving callbacks, and make private object variables possible.
- Access to definition scope. Explain that a closure lets a function access and manipulate external variables and other functions that were in scope when the function was defined.
- Survival of outer variables. Trace why innerFunction can still access innerValue when called after outerFunction has finished: its closure retains access to the scope in which innerFunction was defined.
- Private state through closures. Explain how the Ninja constructor keeps feints inaccessible to outside code while letting its methods read and change it.
- Callback state isolation. Explain how the timer callback accesses variables from animateIt and why keeping those variables local lets multiple animations run without interfering with one another.
- Mutable closure state. Explain why the variables available to the callback through its closure are not merely a snapshot of their values when the closure was created.
- Kinds of execution context. Distinguish the single global execution context from the new function execution context created on each function invocation, and distinguish execution context from the function context accessed through `this`.
- Execution context stack behavior. Trace how function calls push execution contexts onto the call stack and returns pop them, restoring the paused caller; apply this to the section’s `skulk` and `report` calls.
- Identifier to variable mappings. Explain that a lexical environment keeps track of which specific variable an identifier refers to and is consulted when that identifier is accessed.
- Scopes tied to code structures. Describe how lexical environments implement JavaScript scoping and can be associated with functions, blocks, or catch parts, each with separate identifier mappings.
- Nested code and variable access. Trace the nesting of the for loop, report function, skulk function, and global code, and describe which outer variables each inner structure can access.
- Lexical environments during evaluation. Explain when the nested code structures receive associated lexical environments, using an invocation of skulk as an example.
- Lexical environment identifier lookup. Trace how JavaScript resolves an identifier by checking the current lexical environment and then successive outer environments, using intro, action, and ninja in report as examples.
- Function creation environment link. Explain how a function’s [[Environment]] reference, saved when the function is created, determines the outer environment of a new lexical environment when that function is called.
- Variable declaration distinctions. State that JavaScript defines variables with var, let, and const, and that these keywords differ in mutability and in their relationship to the lexical environment.
- Const and Reassignable Variables. Distinguish const from var and let in terms of reassignment. Explain that const requires an initial value and that assigning it a new value later throws an exception without changing it.
- Modification of Const Values. Explain why an object or array held in a const variable can still be modified even though the variable cannot be assigned a completely new value.
- Var and function scope. Explain that var registers a variable in the closest function or global lexical environment, ignoring blocks. Describe why a var declaration inside the example’s loop does not belong to the loop’s block environment.
- Let and const placement. Explain that let and const register variables in the closest lexical environment, whether that is a block, function, or global environment. Locate the example variables in those environments.
- Two phases of code execution. Explain that creating a lexical environment triggers an identifier-registration phase before JavaScript executes the code line by line.
- Environment-specific registration rules. Describe which identifiers the section says are registered in function, global, and block environments, and how existing identifier values are treated.
- Early access to function declarations. Explain why a function declaration can be called before its position in the code, and distinguish it from a function expression or arrow function.
- Closures and scoping rules. Explain that a closure lets a function access variables in scope when the function is created, and that this behavior follows from JavaScript’s scoping rules.
- Per-instance closure-based private variables. Explain why each Ninja instance has its own feints variable and methods that can access it, while outside code cannot access it directly.
- Method-call identifier resolution. Trace how ninja2.getFeints() finds feints, including the new call environment and the constructor environment it searches next.
- Limits of closure-based privacy. Explain why moving ninja1.getFeints to an imposter object does not prevent it from reading the feints variable created when ninja1 was instantiated, and why this makes closure-based privacy a useful approximation rather than a private object variable.
- Animation callback lexical environments. Trace how each call to animateIt gives its timer callback access to that animation’s variables when the interval expires, and explain why this simplifies animating multiple objects.
What it builds on
- Invocation as a constructor (chapter 4) · Explains invoking a function as a constructor.
- The this parameter: introducing the function context (chapter 4) · Introduces the function context accessed through `this`.
- Invoking functions (chapter 4) · Introduces ways functions are invoked.
- Functions as first-class objects (chapter 3) · Functions can be passed around as first-class objects.
- Function declarations and function expressions (chapter 3) · The distinction between function declarations and function expressions.
- Arrow functions (chapter 3) · Arrow functions as another way to define functions.
Section by section
Introduction
The introduction explains why closures matter in JavaScript and links their existence to the way JavaScript scopes work.
- Practical value of closures: Explain how closures simplify complex JavaScript code, particularly code involving callbacks, and make private object variables possible.
Why it matters: These examples show why closures are central to the way JavaScript code is written. - Connection between closures and scope: State the relationship the introduction draws between closures and JavaScript scopes, without describing mechanisms it has not yet taught.
Why it matters: The scope connection gives a reason to study JavaScript scoping when examining closures.
Key terms: closures, callbacks, private object variables, scopes
5.1 Understanding closures
This section explains how closures let functions access variables from their definition scope, even after that scope would otherwise be gone, and notes the memory cost.
- Access to definition scope: Explain that a closure lets a function access and manipulate external variables and other functions that were in scope when the function was defined.
Why it matters: It explains why a function can use names declared outside its own body. - Survival of outer variables: Trace why innerFunction can still access innerValue when called after outerFunction has finished: its closure retains access to the scope in which innerFunction was defined.
Why it matters: It accounts for the surprising result when an inner function runs after its outer function returns. - Closure memory cost: Describe the memory cost of keeping information accessible through a closure and state when that information can be released.
Why it matters: Keeping outer information available to a function has a memory cost.
Key terms: closure, scope, identifiers, innerFunction, outerFunction, innerValue, memory, garbage-collect
5.1 Do you know?
This section poses self-check questions about scopes, identifier tracking, and mutable variables without explaining the answers.
5.2 Putting closures to work
This section introduces a practical look at closures and says the examples will be revisited later to explain how they work.
5.2.1 Mimicking private variables
Shows how constructor-local state and closures can approximate private variables while allowing controlled access through methods.
- Private state through closures: Explain how the Ninja constructor keeps feints inaccessible to outside code while letting its methods read and change it.
Why it matters: This pattern lets an object expose useful operations without exposing its state to uncontrolled changes.
Key terms: closure, private variables, constructor, feints, accessor method, getFeints
5.2.2 Using closures with callbacks
The section uses a timer callback to show how closures preserve separate, changeable state for concurrent animations.
- Callback state isolation: Explain how the timer callback accesses variables from animateIt and why keeping those variables local lets multiple animations run without interfering with one another.
Why it matters: This explains why a callback can keep its own state even when several animations are running. - Mutable closure state: Explain why the variables available to the callback through its closure are not merely a snapshot of their values when the closure was created.
Why it matters: An animation’s callback needs to change its retained state as the animation progresses.
Key terms: closure, callback, setInterval, animateIt, global scope, scope, state
5.3 Tracking code execution with execution contexts
The section explains how JavaScript uses execution contexts and a call stack to track active and paused code as functions call one another and return.
- Kinds of execution context: Distinguish the single global execution context from the new function execution context created on each function invocation, and distinguish execution context from the function context accessed through `this`.
Why it matters: These distinctions clarify what the engine is tracking when it enters a function. - Execution context stack behavior: Trace how function calls push execution contexts onto the call stack and returns pop them, restoring the paused caller; apply this to the section’s `skulk` and `report` calls.
Why it matters: The stack explains how nested calls return to the right place without their callers running at the same time.
Key terms: global code, function code, global execution context, function execution context, function context, this, single-threaded execution model, execution context stack, call stack, push
5.4 Keeping track of identifiers with lexical environments
This section introduces lexical environments as the engine’s way of tracking which variables identifiers refer to, where those environments can occur, and how ES6 expanded scoping beyond functions.
- Identifier to variable mappings: Explain that a lexical environment keeps track of which specific variable an identifier refers to and is consulted when that identifier is accessed.
Why it matters: This gives you a way to describe how JavaScript finds the variable named in an expression. - Scopes tied to code structures: Describe how lexical environments implement JavaScript scoping and can be associated with functions, blocks, or catch parts, each with separate identifier mappings.
Why it matters: It explains why the same identifier can be tracked separately in different parts of a program. - Change from function only scoping: Distinguish the pre-ES6 limitation to function scoping from ES6’s addition of block scoping, and explain why the older behavior confused programmers from other C-like languages.
Why it matters: The distinction helps explain why older JavaScript scoping could surprise programmers accustomed to block scopes.
Key terms: lexical environment, identifiers, variables, lexical environments, scopes, function, block of code, catch part, identifier mappings, pre-ES6
5.4.1 Code nesting
This section uses nested code to show how scopes relate to lexical environments and which outer variables inner code can access.
- Nested code and variable access: Trace the nesting of the for loop, report function, skulk function, and global code, and describe which outer variables each inner structure can access.
Why it matters: The nesting tells you which surrounding variables a piece of code can use. - Lexical environments during evaluation: Explain when the nested code structures receive associated lexical environments, using an invocation of skulk as an example.
Why it matters: This links the section’s nesting example to the lexical environments used to account for scope.
Key terms: code nesting, inner code structure, outer code structures, scope, lexical environment, code evaluation, function invocation
5.4.2 Code nesting and lexical environments
Explains how a function’s creation environment determines the outer environments searched when JavaScript resolves identifiers.
- Lexical environment identifier lookup: Trace how JavaScript resolves an identifier by checking the current lexical environment and then successive outer environments, using intro, action, and ninja in report as examples.
Why it matters: This lets you explain why code can use names that were not defined directly inside its function. - Function creation environment link: Explain how a function’s [[Environment]] reference, saved when the function is created, determines the outer environment of a new lexical environment when that function is called.
Why it matters: The saved creation environment explains which outer variables a called function can access. - Creation versus call location: Justify why searching the execution-context stack is not a general substitute for following a function’s creation-environment link.
Why it matters: It helps distinguish lexical surroundings from the path of function calls.
Key terms: lexical environment, outer environment, identifier, reference error, [[Environment]], execution context, execution context stack, first-class objects
5.5 Understanding types of JavaScript variables
Introduces JavaScript’s three variable-declaration keywords and the two ways the section will compare them.
- Variable declaration distinctions: State that JavaScript defines variables with var, let, and const, and that these keywords differ in mutability and in their relationship to the lexical environment.
Why it matters: These two distinctions frame the more detailed discussion of JavaScript variables.
Key terms: var, let, const, mutability, lexical environment
5.5.1 Variable mutability
This section explains how const differs from var and let, why const is useful, and why a const-bound object or array can still be modified.
- Const and Reassignable Variables: Distinguish const from var and let in terms of reassignment. Explain that const requires an initial value and that assigning it a new value later throws an exception without changing it.
Why it matters: This distinction helps you predict whether an assignment will work or throw an exception. - Modification of Const Values: Explain why an object or array held in a const variable can still be modified even though the variable cannot be assigned a completely new value.
Why it matters: It prevents the common mistake of treating a const-bound object or array as unchangeable. - Purposes of Const Declarations: Describe the two uses of const given here: marking variables that should not be reassigned and naming fixed values instead of repeating literals. Explain the stated benefits.
Why it matters: These uses make the intended role of a value clearer and help prevent accidental reassignment.
Key terms: const, var, let, initialization, reassigned, object, array, modify, fixed value, MAX_RONIN_COUNT
5.5.2 Variable definition keywords and lexical environments
This section contrasts where var, let, and const register variables in lexical environments, using a loop inside a function to show the effect of block scope.
- Var and function scope: Explain that var registers a variable in the closest function or global lexical environment, ignoring blocks. Describe why a var declaration inside the example’s loop does not belong to the loop’s block environment.
Why it matters: It explains why a variable declared with var inside a block can still be accessed outside that block. - Let and const placement: Explain that let and const register variables in the closest lexical environment, whether that is a block, function, or global environment. Locate the example variables in those environments.
Why it matters: It lets you tell whether a declaration belongs to a block, its enclosing function, or the global environment.
Key terms: var, function lexical environment, global lexical environment, block environment, let, const, closest lexical environment, block-scoped
5.5.3 Registering identifiers within lexical environments
This section explains how JavaScript registers identifiers before executing code, and uses that process to explain early function calls, name collisions, and hoisting.
- Two phases of code execution: Explain that creating a lexical environment triggers an identifier-registration phase before JavaScript executes the code line by line.
Why it matters: This distinction explains how an identifier can be available before execution reaches its declaration. - Environment-specific registration rules: Describe which identifiers the section says are registered in function, global, and block environments, and how existing identifier values are treated.
Why it matters: The environment and declaration type determine what the engine registers before execution. - Early access to function declarations: Explain why a function declaration can be called before its position in the code, and distinguish it from a function expression or arrow function.
Why it matters: It explains why moving a function declaration below a call need not break that call, while changing how the function is defined can. - Function and variable name collision: Trace why an identifier shared by a function declaration and a variable declaration initially refers to the function but refers to a number after the assignment `var fun = 3`.
Why it matters: It shows how registration order and a later assignment produce different values for the same name at different points. - Hoisting as a simplification: Distinguish the common description of hoisting from the section’s account of identifier registration.
Why it matters: It gives a more precise explanation of early identifier availability than the image of code being moved.
Key terms: lexical environment, registering identifiers, JavaScript execution, function environment, global environment, block environment, arguments, function declarations, var, function declaration
5.6 Exploring how closures work
This introduction connects closures to JavaScript scoping rules before revisiting earlier examples.
- Closures and scoping rules: Explain that a closure lets a function access variables in scope when the function is created, and that this behavior follows from JavaScript’s scoping rules.
Why it matters: This connection helps explain why closures can be used for private-like variables and callbacks.
Key terms: closures, scope, scoping rules
5.6.1 Revisiting mimicking private variables with closures
This section explains how constructor-created closures approximate private variables and how a method call finds one of those variables through lexical environments.
- Per-instance closure-based private variables: Explain why each Ninja instance has its own feints variable and methods that can access it, while outside code cannot access it directly.
Why it matters: It explains both why the methods can use feints and why separate Ninja objects do not share that constructor-local variable. - Method-call identifier resolution: Trace how ninja2.getFeints() finds feints, including the new call environment and the constructor environment it searches next.
Why it matters: Following the lookup step by step shows how a method can read a constructor-local variable after construction.
Key terms: Ninja constructor, lexical environment, closure, feints, getFeints, feint, ninja2.getFeints(), execution context, outer environment
5.6.2 Private variables caveat
This section shows why closure-based “private” variables belong to a function’s closure rather than to the object holding the method.
- Limits of closure-based privacy: Explain why moving ninja1.getFeints to an imposter object does not prevent it from reading the feints variable created when ninja1 was instantiated, and why this makes closure-based privacy a useful approximation rather than a private object variable.
Why it matters: It clarifies what closure-based information hiding protects—and what it does not make private to an object.
Key terms: closure, private object variables, getFeints, feints, imposter object
5.6.3 Revisiting the closures and callbacks example
Explains how closures let separate timer callbacks retain the variables for their own animations.
- Animation callback lexical environments: Trace how each call to animateIt gives its timer callback access to that animation’s variables when the interval expires, and explain why this simplifies animating multiple objects.
Why it matters: This explains how multiple timed animations can each keep track of their own state without extra bookkeeping.
Key terms: closure, function lexical environment, setInterval callback, clearInterval, animateIt
5.7 Summary
5.8 Exercises
Where this is used later
- Instance properties (chapter 7) · Mimicking private variables with closures
- Controlling access to properties with getters and setters (chapter 8) · Closures can mimic private variables.
- Defining getters and setters (chapter 8) · Using closures to mimic private variables.
- Exploring generators under the hood (chapter 6) · Execution contexts and the execution context stack.
Concepts explained in this chapter
- lexical environmentAn internal JavaScript structure that holds identifier bindings and links to an outer environment for name resolution.
- closureA function together with the lexical environment in which it was created, allowing it to access variables from that environment after the outer function has finished executing.
- execution contextAn internal JavaScript runtime record that tracks the code being evaluated and the environment used to resolve identifiers.
- private variableA variable intended to be inaccessible from outside an abstraction boundary, such as an object or function scope.
- identifier resolutionThe process of determining which binding a given identifier refers to in a particular program context.
- function declarationA JavaScript statement that defines a named function.
- const qualifierA JavaScript binding declared with const that must be initialized at declaration and cannot be reassigned, although an object or array it references can still be modified.
- execution context stackA stack-like runtime structure that records active execution contexts for nested calls and returns.
- hoistingA common description of JavaScript declaration handling as moving declarations to the top of a scope, although declarations are registered before code executes rather than literally moved.
- identifier registrationThe pre-execution process of recording declared identifiers and their initial bindings in a lexical environment.
- function execution contextAn execution context created for evaluating a particular function invocation in JavaScript.
- global execution contextThe global execution context and its associated object value, which can serve as this in non-strict function calls without an explicit receiver.
- [[Environment]]The internal function property that stores a reference to the lexical environment in which the function was created.
- callbackA function supplied to other code to be invoked later when a condition or event occurs.
- scopeThe region in which an identifier's binding is visible and can be resolved.
- variable declaration keywordA JavaScript keyword used to declare a variable binding, such as var, let, or const.
- identifierA name used to identify an entity in a program.
- accessor methodA method used to retrieve or update a property or encapsulated state.
- function scopeA scope in which a variable is visible throughout the entire function body rather than being limited to a block.
- thisThe implicit JavaScript function binding whose value is determined by how the function is called.
- function expressionA function defined as an expression rather than a declaration, producing a function value that can be assigned or passed around.
These guides are generated from the text of Manning books by Manning's knowledge graph and link to the sections they draw on. The full books are available on liveBook and at manning.com.
