An Environment Record maps identifier names to bindings for one scope. Every record has an [[OuterEnv]] field pointing to the surrounding record (or null), so records form the scope chain. Each time a function, block, or catch clause is evaluated, a new record is created; that is why two calls to the same function never share locals.
They are a specification device. An engine may store the bindings in registers, stack slots, or heap objects, and no JavaScript code can obtain a record.
The spec defines an abstract type and these concrete kinds:
| Kind | Used for | Notable |
|---|---|---|
| Declarative | Blocks, catch, let/const/class, plain scope |
Bindings are stored directly and can be mutable, immutable, or uninitialized |
| Function (subtype) | One function invocation | Adds the this binding, the function object, and new.target; arrow functions don't have their own this |
| Module (subtype) | A module's top level | Adds indirect immutable bindings for imports, which read the exporter's live binding |
| Object | with statements and the global object |
Bindings are the properties of an object; with consults Symbol.unscopables |
| Global | Scripts' top level | A composite of an Object record (the global object) and a Declarative record (let/const/class) |
Each binding has attributes: mutable or immutable, strict, deletable, initialized or not. These explain the observable rules: a const is an immutable binding (assignment throws a TypeError), a let before its declaration is an uninitialized binding (reads throw a ReferenceError), and a closure is a function whose [[Environment]] points at a record that outlives its call.
The operations are HasBinding, CreateMutableBinding, CreateImmutableBinding, InitializeBinding, SetMutableBinding, GetBindingValue, and DeleteBinding. Name resolution is HasBinding repeated along [[OuterEnv]].