There are two variables with the same name (name
) in that snippet. If you look closely at the generated AST, you will see both declarations.
The AST does not show the scopes of the two declarations, which is an important aspect. The scope of a variable is the region of the program in which the variable's name is visible; that is, associated with the variable. It is perfectly legal for two variables to have the same name; if they have overlapping scopes, the inner scope will override the outer scope.
All of that is common to most programming languages, but ECMAScript's scoping rules are rather more complicated than average (which, in part, is why it would be useful for the AST tool to show scopes). ECMAScript features two different sets of scoping rules, one for variables declared with var
, and another for variables declared with let
or const
.
While this simple example doesn't show all the differences, it certainly does show one important difference: while the inner var
-declared name
can be used before it has been initialised, resulting in the value undefined
, the let
-declared age
cannot be used before initialisation, resulting in an error being thrown. (let
-declared variables are in what's called a "Temporal Dead Zone" until they are initialised.)
The difference which is not shown in this example is the boundaries of the scopes of the variables declared inside the function sayHi
. The scope of a var
-declared variable is the innermost function body which it is declared in (including any functions declared in the same scope, unless they have an overriding declaration of the same name). The scope of a let
-declared variable, on the other hand, is limited to the innermost enclosing block. But in this example, the innermost enclosing block of both the inner var name
and let age
is the body of the function sayHi
.
In ECMAScript, the scope of a variable is always an entire block. This contrasts with languages such as C in which the scope of a variable only extends from the variable's declaration until the end of the enclosing block. This would make a difference in this case: Under C scoping rules, name
in console.log(name)
would refer to the outer declaration, because the inner declaration is further down in the code and so its scope hasn't started yet. And age
in console.log(age)
would be illegal because there is no enclosing scope in which age
is declared. But in ECMAScript, declarations are "hoisted" to encompass the entire block, creating the possibility of Temporal Dead Zones.
While this is probably already Too Much Information, I should note that I haven't really covered all of the odd wrinkles in scoping in either ECMAScript or C, and that other languages have other subtly different scoping rules.