I believe it’s better to be empowered by knowledge of how things work than to just gloss over details with assumptions and lack of curiosity. You won’t get to spend all your time riding on the smooth happy path. Wouldn’t you rather be prepared for the inevitable bumps?
Implied Scopes
Scopes are sometimes created in non-obvious places. In practice, these implied scopes don’t often impact your program behavior, but it’s still useful to know they’re happening.Parameter Scope
The conversation metaphor in Chapter 2 implies that function parameters are basically the same as locally declared variables in the function scope. But that’s not always true. Consider:studentID is a “simple” parameter, so it does behave as a member of the BLUE(2) function scope. But if we change it to be a non-simple parameter, that’s no longer technically the case.
Parameter forms considered non-simple include parameters with default values, rest parameters (using ...), and destructured parameters.
var id = 5 is shadowing the id parameter, but the closure of the defaultID() function is over the parameter, not the shadowing variable in the function body. This proves there’s a scope bubble around the parameter list.
Function Name Scope
The name identifier of a function expression is in its own implied scope, nested between the outer enclosing scope and the main inner function scope.let declaration form does not allow re-declaration. But this is perfectly legal shadowing, not re-declaration, because the two ofTheTeacher identifiers are in separate scopes.
Anonymous vs. Named Functions
As discussed in Chapter 3, functions can be expressed in named or anonymous form. It’s vastly more common to use the anonymous form, but is that a good idea?As you contemplate naming your functions, consider:
- Name inference is incomplete
- Lexical names allow self-reference
- Names are useful descriptions
- Arrow functions have no lexical names
- IIFEs also need names
Why Names Matter
First of all, “anonymous” showing up in stack traces is not helpful to debugging:Missing Names?
Name inference is incomplete. Anonymousfunction expressions passed as callbacks are incapable of receiving an inferred name:
function expressions, especially anonymous ones, are used as callback arguments; none of these get a name.
Names are Descriptors
Leaving off a name from a function makes it harder for the reader to tell what the function’s purpose is. Consider:Arrow Functions
Arrow functions are always anonymous, even if they’re used in a way that gives them an inferred name. Don’t use them as a general replacement for regular functions. They’re more concise, yes, but that brevity comes at the cost of omitting key visual delimiters that help our brains quickly parse out what we’re reading. Arrow functions have a purpose: lexical this behavior. Arrow functions don’t define athis identifier keyword at all. If you use a this inside an arrow function, it behaves exactly as any other variable reference.
IIFE Variations
All functions should have names, including IIFEs:StoreStudentRecords because that’s what it’s doing.
Hoisting: Functions and Variables
Chapter 5 articulated both function hoisting and variable hoisting. Let’s explore the merits of each.Function Hoisting
This program works because of function hoisting:getStudents(), I like that its first line is also executable code.
Variable Hoisting
In almost all cases, I agree that variable hoisting is a bad idea. But there’s one exception: placingvar declarations in CommonJS modules.
Here’s how I typically structure my module definitions in Node:
cache and otherData variables are in the “private” section because I don’t plan to expose them publicly. But I’ve had rare cases where I needed the assignments to happen above, before I declare the exported public API.
That’s literally the only case I’ve ever found for leveraging variable hoisting.
The Case for var
Let’s have some real talk about var:
Key points:
varwas never brokenletis your friendconsthas limited utility- The best of both worlds:
varandlet
Don’t Throw Out var
var is fine, and works just fine. It’s been around for 25 years, and it’ll be around for another 25 years or more. Claims that var is broken, deprecated, or dangerous are bogus bandwagoning.
Does that mean var is the right declarator for every declaration? Certainly not. But it still has its place.
For the record, I’m a fan of let, for block-scoped declarations. I use it often. In fact, I probably use it as much or more than I use var.
const-antly Confused
const pretends to create values that can’t be mutated—a misconception that’s extremely common—whereas what it really does is prevent re-assignment.
const is when I’m assigning an already-immutable value (like 42 or "Hello, friends!"), and when it’s clearly a “constant” in the sense of being a named placeholder for a literal value.
var and let
The fact is, you should be using both var and let in your programs. They are not interchangeable.
So where should we still use var? I always use var in the top-level scope of any function, regardless of whether that’s at the beginning, middle, or end of the function. I also use var in the global scope.
By contrast, I rarely use a var inside a block. That’s what let is for.
studentRecords variable is intended for use across the whole function. var is the best declarator to tell the reader that. By contrast, record and id are intended for use only in the loop iteration, so let is the best tool.
What’s the Deal with TDZ?
The TDZ (temporal dead zone) was explained in Chapter 5. Let’s look briefly at the motivations.Where It All Started
TDZ comes fromconst, actually. During early ES6 development work, TC39 had to decide whether const (and let) were going to hoist to the top of their blocks. They decided these declarations would hoist.
But if let and const hoist to the top of the block, why don’t they auto-initialize (to undefined) the way var does? Here was the main concern:
studentName not only hoisted to the top of this block, but was also auto-initialized to undefined. For the first half of the block, studentName could be observed to have the undefined value. Once the const studentName = .. statement is reached, now studentName is assigned "Frank".
We can’t auto-initialize studentName to undefined. But the variable has to exist throughout the whole scope. What do we do with the period of time from when it first exists (beginning of scope) and when it’s assigned its value?
We call this period of time the “dead zone,” as in the “temporal dead zone” (TDZ). To prevent confusion, it was determined that any sort of access of a variable while in its TDZ is illegal and must result in an error.
Who let the TDZ Out?
TC39 made the decision: since we need a TDZ for const, we might as well have a TDZ for let as well. In fact, if we make let have a TDZ, then we discourage all that ugly variable hoisting people do.
My counter-argument: if you’re favoring consistency, be consistent with var instead of const; let is definitely more like var than const.
But alas, that’s not how it landed. let has a TDZ because const needs a TDZ, because let and const mimic var in their hoisting to the top of the (block) scope.
Are Synchronous Callbacks Still Closures?
Chapter 7 presented two different models for tackling closure. These models are not wildly divergent, but they do approach from a different perspective, and that changes what we identify as a closure.What is a Callback?
Let’s first consider an asynchronous callback:Synchronous Callback?
But what about synchronous callbacks?formatIDLabel(..) as a callback? There’s nothing to call back into per se, because the program hasn’t paused or exited.
What’s the relationship between an asynchronous callback and an IIF? An asynchronous callback is an IIF that’s invoked asynchronously instead of synchronously.
Synchronous Closure?
Now that we’ve re-labeled synchronous callbacks as IIFs, are IIFs an example of closure?renderLabel(..) IIF references list from the enclosing scope. But here’s where the definition/model we choose for closure matters:
-
If
renderLabel(..)is a function that gets passed somewhere else and invoked, then yes, it’s exercising closure. -
But if
renderLabel(..)stays in place, and only a reference to it is passed toforEach(..), is there any need for closure to preserve the scope chain while it executes synchronously right inside its own scope?

