How is a Variable Resolved to the Value in JavaScript?

Published on

Indicating a variable in the code is done by using an IdentifierReference, which is a type of identifier. The ECMAScript specification defines three kinds of identifiers:

  • An IdentifierReference is used to refer to an existing variable or value.
  • A BindingIdentifier is used when declaring a variable.
  • A LabelIdentifier is used as a label for break or continue statements.
let a = 10;
-

BindingIdentifier

let b = 5;
b;
-

IdentifierReference

function* gen() {
let yield;
-----

BindingIdentifier

yield 1;
-----

IdentifierReference

}
async function asyncFunc() {
let await;
-----

BindingIdentifier

await something();
-----

IdentifierReference

}
loop1: for (let i = 0; i < 10; i++) {
break loop1;
-----

LabelIdentifier

}

Step By Step

Start from evaluating a IdentifierReference:

Runtime Semantics: Evaluation | Expressions

**Runtime Semantics: Evaluation**
IdentifierReference : Identifier
1. Return ? ResolveBinding(StringValue of Identifier).

ResolveBinding ( name [ , env ] ) | Execution Contexts

**ResolveBinding ( name [ , env ] )**
The abstract operation ResolveBinding takes argument name (a String) and optional argument env (an Environment Record or undefined) and returns either a normal completion containing a Reference Record or an abrupt completion. It is used to determine the binding of name. env can be used to explicitly provide the Environment Record that is to be searched for the binding. It performs the following steps when called:
1. If env is not present or if env is undefined, then
a. Set env to the running execution context's LexicalEnvironment.
2. Assert: env is an Environment Record.
3. If the source text matched by the syntactic production that is being evaluated is contained in strict mode code, let strict be true; else let strict be false.
4. Return ? GetIdentifierReference(env, name, strict).
Note
The result of ResolveBinding is always a Reference Record whose [[ReferencedName]] field is name.

GetIdentifierReference ( env, name, strict ) | Environment Records**

**GetIdentifierReference ( env, name, strict )**
The abstract operation GetIdentifierReference takes arguments env (an Environment Record or null), name (a String), and strict (a Boolean) and returns either a normal completion containing a Reference Record or an abrupt completion. It performs the following steps when called:
1. If env is the value null, then
a. Return the Reference Record { [[Base]]: unresolvable, [[ReferencedName]]: name, [[Strict]]: strict, [[ThisValue]]: empty }.
2. Let exists be ? env.HasBinding(name).
3. If exists is true, then
a. Return the Reference Record { [[Base]]: env, [[ReferencedName]]: name, [[Strict]]: strict, [[ThisValue]]: empty }.
4. Else,
a. Let outer be env.[[OuterEnv]].
b. Return ? GetIdentifierReference(outer, name, strict).

The Reference Record Specification Type

A Reference Record is a resolved name or property binding.

A Reference Record consists of 4 fields, a [[Base]] value, a [[ReferencedName]] field, a Boolean-valued [[Strict]] flag, and a [[ThisValue]] field.

The base value field is either undefined, an Object, a Boolean, a String, a Symbol, a Number, a BigInt, an Environment Record, or unresolvable. A base value component of unresolvable indicates that the Reference could not be resolved to a binding.

The referenced name field is the name of the binding. It is always a String if [[Base]] value is an Environment Record.

When the code need the value of evaluating Expression, it will use getValue(refRecord). Take Logical NOT Operator (!) for example:

**Runtime Semantics: Evaluation**
UnaryExpression: ! UnaryExpression
1. Let expr be the result of evaluating UnaryExpression.
2. Let oldValue be ToBoolean(? GetValue(expr)).
3. If oldValue is true, return false.
4. Return true.

GetValue ( V )

**GetValue ( V )**
The abstract operation GetValue takes argument V and returns either a normal completion containing an ECMAScript language value or an abrupt completion. It performs the following steps when called:
1. ReturnIfAbrupt(V).
2. If V is not a Reference Record, return V.
3. If IsUnresolvableReference(V) is true, throw a ReferenceError exception.
4. If IsPropertyReference(V) is true, then
a. Let baseObj be ? ToObject(V.[[Base]]).
b. If IsPrivateReference(V) is true, then
i. Return ? PrivateGet(baseObj, V.[[ReferencedName]]).
c. Return ? baseObj.[[Get]](V.[[ReferencedName]], GetThisValue(V)).
5. Else,
a. Let base be V.[[Base]].
b. Assert: base is an Environment Record.
c. Return ? base.GetBindingValue(V.[[ReferencedName]], V.[[Strict]]) (see 9.1).
Note
The object that may be created in step 4.a is not accessible outside of the above abstract operation and the ordinary object [[Get]] internal method. An implementation might choose to avoid the actual creation of the object.

Summary

  1. The engine should start from the environment record of running execution context’s LexicalEnvironment to find the binding with the given name. If not found in an environment’s record, look up to its parent environment’s outer environment until the binding is found or no outer environment.

  2. The Reference would be

    {
    [[Base]]: which environment record the binding found,
    [[ReferencedName]]: binding name
    }
  3. getValue(refRecord) is equal to refRecord.[[Base]].GetBindingValue(refRecord.[[ReferencedName]])

The way a variable is resolved, along with the mechanisms explained in previous articles in this series about how declarations are created to bind to specific environment records, gives a thorough explanation of how variable scope works according to the ECMAScript specification.